Bug 1554951 [wpt PR 17043] - wake-lock: Fix invalid types test in wakelock-type.https...
[gecko.git] / dom / serviceworkers / ServiceWorkerPrivate.cpp
blob381f2b9cab6df8eedeb078ad0358cd157f40f1db
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 "ServiceWorkerPrivate.h"
9 #include "ServiceWorkerCloneData.h"
10 #include "ServiceWorkerManager.h"
11 #include "nsContentUtils.h"
12 #include "nsICacheInfoChannel.h"
13 #include "nsIHttpChannelInternal.h"
14 #include "nsIHttpHeaderVisitor.h"
15 #include "nsINamed.h"
16 #include "nsINetworkInterceptController.h"
17 #include "nsIPushErrorReporter.h"
18 #include "nsISupportsImpl.h"
19 #include "nsITimedChannel.h"
20 #include "nsIUploadChannel2.h"
21 #include "nsNetUtil.h"
22 #include "nsProxyRelease.h"
23 #include "nsQueryObject.h"
24 #include "nsStreamUtils.h"
25 #include "nsStringStream.h"
26 #include "mozilla/Assertions.h"
27 #include "mozilla/CycleCollectedJSContext.h" // for MicroTaskRunnable
28 #include "mozilla/JSObjectHolder.h"
29 #include "mozilla/dom/Client.h"
30 #include "mozilla/dom/ClientIPCTypes.h"
31 #include "mozilla/dom/FetchUtil.h"
32 #include "mozilla/dom/IndexedDatabaseManager.h"
33 #include "mozilla/dom/InternalHeaders.h"
34 #include "mozilla/dom/NotificationEvent.h"
35 #include "mozilla/dom/PromiseNativeHandler.h"
36 #include "mozilla/dom/PushEventBinding.h"
37 #include "mozilla/dom/RequestBinding.h"
38 #include "mozilla/dom/WorkerDebugger.h"
39 #include "mozilla/dom/WorkerRef.h"
40 #include "mozilla/dom/WorkerRunnable.h"
41 #include "mozilla/dom/WorkerScope.h"
42 #include "mozilla/dom/ipc/StructuredCloneData.h"
43 #include "mozilla/net/CookieSettings.h"
44 #include "mozilla/net/NeckoChannelParams.h"
45 #include "mozilla/StaticPrefs.h"
46 #include "mozilla/Unused.h"
47 #include "nsIReferrerInfo.h"
49 using namespace mozilla;
50 using namespace mozilla::dom;
52 namespace mozilla {
53 namespace dom {
55 using mozilla::ipc::PrincipalInfo;
57 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(ServiceWorkerPrivate)
58 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(ServiceWorkerPrivate)
59 NS_IMPL_CYCLE_COLLECTION(ServiceWorkerPrivate, mSupportsArray)
60 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ServiceWorkerPrivate, AddRef)
61 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ServiceWorkerPrivate, Release)
63 // Tracks the "dom.serviceWorkers.disable_open_click_delay" preference. Modified
64 // on main thread, read on worker threads.
65 // It is updated every time a "notificationclick" event is dispatched. While
66 // this is done without synchronization, at the worst, the thread will just get
67 // an older value within which a popup is allowed to be displayed, which will
68 // still be a valid value since it was set prior to dispatching the runnable.
69 Atomic<uint32_t> gDOMDisableOpenClickDelay(0);
71 // Used to keep track of pending waitUntil as well as in-flight extendable
72 // events. When the last token is released, we attempt to terminate the worker.
73 class KeepAliveToken final : public nsISupports {
74 public:
75 NS_DECL_ISUPPORTS
77 explicit KeepAliveToken(ServiceWorkerPrivate* aPrivate) : mPrivate(aPrivate) {
78 MOZ_ASSERT(NS_IsMainThread());
79 MOZ_ASSERT(aPrivate);
80 mPrivate->AddToken();
83 private:
84 ~KeepAliveToken() {
85 MOZ_ASSERT(NS_IsMainThread());
86 mPrivate->ReleaseToken();
89 RefPtr<ServiceWorkerPrivate> mPrivate;
92 NS_IMPL_ISUPPORTS0(KeepAliveToken)
94 ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo)
95 : mInfo(aInfo), mDebuggerCount(0), mTokenCount(0) {
96 MOZ_ASSERT(NS_IsMainThread());
97 MOZ_ASSERT(aInfo);
99 mIdleWorkerTimer = NS_NewTimer();
100 MOZ_ASSERT(mIdleWorkerTimer);
103 ServiceWorkerPrivate::~ServiceWorkerPrivate() {
104 MOZ_ASSERT(!mWorkerPrivate);
105 MOZ_ASSERT(!mTokenCount);
106 MOZ_ASSERT(!mInfo);
107 MOZ_ASSERT(mSupportsArray.IsEmpty());
109 mIdleWorkerTimer->Cancel();
112 namespace {
114 class CheckScriptEvaluationWithCallback final : public WorkerRunnable {
115 nsMainThreadPtrHandle<ServiceWorkerPrivate> mServiceWorkerPrivate;
116 nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
118 // The script evaluation result must be reported even if the runnable
119 // is cancelled.
120 RefPtr<LifeCycleEventCallback> mScriptEvaluationCallback;
122 #ifdef DEBUG
123 bool mDone;
124 #endif
126 public:
127 CheckScriptEvaluationWithCallback(
128 WorkerPrivate* aWorkerPrivate,
129 ServiceWorkerPrivate* aServiceWorkerPrivate,
130 KeepAliveToken* aKeepAliveToken,
131 LifeCycleEventCallback* aScriptEvaluationCallback)
132 : WorkerRunnable(aWorkerPrivate),
133 mServiceWorkerPrivate(new nsMainThreadPtrHolder<ServiceWorkerPrivate>(
134 "CheckScriptEvaluationWithCallback::mServiceWorkerPrivate",
135 aServiceWorkerPrivate)),
136 mKeepAliveToken(new nsMainThreadPtrHolder<KeepAliveToken>(
137 "CheckScriptEvaluationWithCallback::mKeepAliveToken",
138 aKeepAliveToken)),
139 mScriptEvaluationCallback(aScriptEvaluationCallback)
140 #ifdef DEBUG
142 mDone(false)
143 #endif
145 MOZ_ASSERT(NS_IsMainThread());
148 ~CheckScriptEvaluationWithCallback() { MOZ_ASSERT(mDone); }
150 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
151 aWorkerPrivate->AssertIsOnWorkerThread();
153 bool fetchHandlerWasAdded = aWorkerPrivate->FetchHandlerWasAdded();
154 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<bool>(
155 "dom::CheckScriptEvaluationWithCallback::ReportFetchFlag", this,
156 &CheckScriptEvaluationWithCallback::ReportFetchFlag,
157 fetchHandlerWasAdded);
158 aWorkerPrivate->DispatchToMainThread(runnable.forget());
160 ReportScriptEvaluationResult(
161 aWorkerPrivate->WorkerScriptExecutedSuccessfully());
163 return true;
166 void ReportFetchFlag(bool aFetchHandlerWasAdded) {
167 MOZ_ASSERT(NS_IsMainThread());
168 mServiceWorkerPrivate->SetHandlesFetch(aFetchHandlerWasAdded);
171 nsresult Cancel() override {
172 ReportScriptEvaluationResult(false);
173 return WorkerRunnable::Cancel();
176 private:
177 void ReportScriptEvaluationResult(bool aScriptEvaluationResult) {
178 #ifdef DEBUG
179 mDone = true;
180 #endif
181 mScriptEvaluationCallback->SetResult(aScriptEvaluationResult);
182 MOZ_ALWAYS_SUCCEEDS(
183 mWorkerPrivate->DispatchToMainThread(mScriptEvaluationCallback));
187 } // anonymous namespace
189 nsresult ServiceWorkerPrivate::CheckScriptEvaluation(
190 LifeCycleEventCallback* aScriptEvaluationCallback) {
191 nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent);
192 NS_ENSURE_SUCCESS(rv, rv);
194 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
195 RefPtr<WorkerRunnable> r = new CheckScriptEvaluationWithCallback(
196 mWorkerPrivate, this, token, aScriptEvaluationCallback);
197 if (NS_WARN_IF(!r->Dispatch())) {
198 return NS_ERROR_FAILURE;
201 return NS_OK;
204 namespace {
206 enum ExtendableEventResult { Rejected = 0, Resolved };
208 class ExtendableEventCallback {
209 public:
210 virtual void FinishedWithResult(ExtendableEventResult aResult) = 0;
212 NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
215 class KeepAliveHandler final : public ExtendableEvent::ExtensionsHandler,
216 public PromiseNativeHandler {
217 // This class manages lifetime extensions added by calling WaitUntil()
218 // or RespondWith(). We allow new extensions as long as we still hold
219 // |mKeepAliveToken|. Once the last promise was settled, we queue a microtask
220 // which releases the token and prevents further extensions. By doing this,
221 // we give other pending microtasks a chance to continue adding extensions.
223 RefPtr<StrongWorkerRef> mWorkerRef;
224 nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
226 // We start holding a self reference when the first extension promise is
227 // added. As far as I can tell, the only case where this is useful is when
228 // we're waiting indefinitely on a promise that's no longer reachable
229 // and will never be settled.
230 // The cycle is broken when the last promise was settled or when the
231 // worker is shutting down.
232 RefPtr<KeepAliveHandler> mSelfRef;
234 // Called when the last promise was settled.
235 RefPtr<ExtendableEventCallback> mCallback;
237 uint32_t mPendingPromisesCount;
239 // We don't actually care what values the promises resolve to, only whether
240 // any of them were rejected.
241 bool mRejected;
243 public:
244 NS_DECL_ISUPPORTS
246 explicit KeepAliveHandler(
247 const nsMainThreadPtrHandle<KeepAliveToken>& aKeepAliveToken,
248 ExtendableEventCallback* aCallback)
249 : mKeepAliveToken(aKeepAliveToken),
250 mCallback(aCallback),
251 mPendingPromisesCount(0),
252 mRejected(false) {
253 MOZ_ASSERT(mKeepAliveToken);
256 bool Init() {
257 MOZ_ASSERT(IsCurrentThreadRunningWorker());
259 RefPtr<KeepAliveHandler> self = this;
260 mWorkerRef = StrongWorkerRef::Create(GetCurrentThreadWorkerPrivate(),
261 "KeepAliveHandler",
262 [self]() { self->MaybeCleanup(); });
264 if (NS_WARN_IF(!mWorkerRef)) {
265 return false;
268 return true;
271 bool WaitOnPromise(Promise& aPromise) override {
272 if (!mKeepAliveToken) {
273 MOZ_ASSERT(!mSelfRef, "We shouldn't be holding a self reference!");
274 return false;
276 if (!mSelfRef) {
277 MOZ_ASSERT(!mPendingPromisesCount);
278 mSelfRef = this;
281 ++mPendingPromisesCount;
282 aPromise.AppendNativeHandler(this);
284 return true;
287 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
288 RemovePromise(Resolved);
291 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override {
292 RemovePromise(Rejected);
295 void MaybeDone() {
296 MOZ_ASSERT(IsCurrentThreadRunningWorker());
298 if (mPendingPromisesCount || !mKeepAliveToken) {
299 return;
301 if (mCallback) {
302 mCallback->FinishedWithResult(mRejected ? Rejected : Resolved);
305 MaybeCleanup();
308 private:
309 ~KeepAliveHandler() { MaybeCleanup(); }
311 void MaybeCleanup() {
312 MOZ_ASSERT(IsCurrentThreadRunningWorker());
314 if (!mKeepAliveToken) {
315 return;
318 mWorkerRef = nullptr;
319 mKeepAliveToken = nullptr;
320 mSelfRef = nullptr;
323 class MaybeDoneRunner : public MicroTaskRunnable {
324 public:
325 explicit MaybeDoneRunner(KeepAliveHandler* aHandler) : mHandler(aHandler) {}
326 virtual void Run(AutoSlowOperation& aAso) override {
327 mHandler->MaybeDone();
330 RefPtr<KeepAliveHandler> mHandler;
333 void RemovePromise(ExtendableEventResult aResult) {
334 MOZ_ASSERT(IsCurrentThreadRunningWorker());
335 MOZ_DIAGNOSTIC_ASSERT(mPendingPromisesCount > 0);
337 // Note: mSelfRef and mKeepAliveToken can be nullptr here
338 // if MaybeCleanup() was called just before a promise
339 // settled. This can happen, for example, if the
340 // worker thread is being terminated for running too
341 // long, browser shutdown, etc.
343 mRejected |= (aResult == Rejected);
345 --mPendingPromisesCount;
346 if (mPendingPromisesCount) {
347 return;
350 CycleCollectedJSContext* cx = CycleCollectedJSContext::Get();
351 MOZ_ASSERT(cx);
353 RefPtr<MaybeDoneRunner> r = new MaybeDoneRunner(this);
354 cx->DispatchToMicroTask(r.forget());
358 NS_IMPL_ISUPPORTS0(KeepAliveHandler)
360 class RegistrationUpdateRunnable : public Runnable {
361 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
362 const bool mNeedTimeCheck;
364 public:
365 RegistrationUpdateRunnable(
366 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
367 bool aNeedTimeCheck)
368 : Runnable("dom::RegistrationUpdateRunnable"),
369 mRegistration(aRegistration),
370 mNeedTimeCheck(aNeedTimeCheck) {
371 MOZ_DIAGNOSTIC_ASSERT(mRegistration);
374 NS_IMETHOD
375 Run() override {
376 if (mNeedTimeCheck) {
377 mRegistration->MaybeScheduleTimeCheckAndUpdate();
378 } else {
379 mRegistration->MaybeScheduleUpdate();
381 return NS_OK;
385 class ExtendableEventWorkerRunnable : public WorkerRunnable {
386 protected:
387 nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
389 public:
390 ExtendableEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
391 KeepAliveToken* aKeepAliveToken)
392 : WorkerRunnable(aWorkerPrivate) {
393 MOZ_ASSERT(NS_IsMainThread());
394 MOZ_ASSERT(aWorkerPrivate);
395 MOZ_ASSERT(aKeepAliveToken);
397 mKeepAliveToken = new nsMainThreadPtrHolder<KeepAliveToken>(
398 "ExtendableEventWorkerRunnable::mKeepAliveToken", aKeepAliveToken);
401 nsresult DispatchExtendableEventOnWorkerScope(
402 JSContext* aCx, WorkerGlobalScope* aWorkerScope, ExtendableEvent* aEvent,
403 ExtendableEventCallback* aCallback) {
404 MOZ_ASSERT(aWorkerScope);
405 MOZ_ASSERT(aEvent);
406 nsCOMPtr<nsIGlobalObject> sgo = aWorkerScope;
407 WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
409 RefPtr<KeepAliveHandler> keepAliveHandler =
410 new KeepAliveHandler(mKeepAliveToken, aCallback);
411 if (NS_WARN_IF(!keepAliveHandler->Init())) {
412 return NS_ERROR_FAILURE;
415 // This must always be set *before* dispatching the event, otherwise
416 // waitUntil calls will fail.
417 aEvent->SetKeepAliveHandler(keepAliveHandler);
419 ErrorResult result;
420 aWorkerScope->DispatchEvent(*aEvent, result);
421 if (NS_WARN_IF(result.Failed())) {
422 result.SuppressException();
423 return NS_ERROR_FAILURE;
426 // [[ If e’s extend lifetime promises is empty, unset e’s extensions allowed
427 // flag and abort these steps. ]]
428 keepAliveHandler->MaybeDone();
430 // We don't block the event when getting an exception but still report the
431 // error message.
432 // Report exception message. Note: This will not stop the event.
433 if (internalEvent->mFlags.mExceptionWasRaised) {
434 result.SuppressException();
435 return NS_ERROR_XPC_JS_THREW_EXCEPTION;
438 return NS_OK;
442 class SendMessageEventRunnable final : public ExtendableEventWorkerRunnable {
443 const ClientInfoAndState mClientInfoAndState;
444 RefPtr<ServiceWorkerCloneData> mData;
446 public:
447 SendMessageEventRunnable(WorkerPrivate* aWorkerPrivate,
448 KeepAliveToken* aKeepAliveToken,
449 const ClientInfoAndState& aClientInfoAndState,
450 RefPtr<ServiceWorkerCloneData>&& aData)
451 : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken),
452 mClientInfoAndState(aClientInfoAndState),
453 mData(std::move(aData)) {
454 MOZ_ASSERT(NS_IsMainThread());
455 MOZ_DIAGNOSTIC_ASSERT(mData);
458 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
459 JS::Rooted<JS::Value> messageData(aCx);
460 nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
461 ErrorResult rv;
462 mData->Read(aCx, &messageData, rv);
464 // If deserialization fails, we will fire a messageerror event
465 bool deserializationFailed = rv.ErrorCodeIs(NS_ERROR_DOM_DATA_CLONE_ERR);
467 if (!deserializationFailed && NS_WARN_IF(rv.Failed())) {
468 return true;
471 Sequence<OwningNonNull<MessagePort>> ports;
472 if (!mData->TakeTransferredPortsAsSequence(ports)) {
473 return true;
476 RootedDictionary<ExtendableMessageEventInit> init(aCx);
478 init.mBubbles = false;
479 init.mCancelable = false;
481 // On a messageerror event, we disregard ports:
482 // https://w3c.github.io/ServiceWorker/#service-worker-postmessage
483 if (!deserializationFailed) {
484 init.mData = messageData;
485 init.mPorts = ports;
488 init.mSource.SetValue().SetAsClient() =
489 new Client(sgo, mClientInfoAndState);
491 rv = NS_OK;
492 RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
493 RefPtr<ExtendableMessageEvent> extendableEvent =
494 ExtendableMessageEvent::Constructor(
495 target,
496 deserializationFailed ? NS_LITERAL_STRING("messageerror")
497 : NS_LITERAL_STRING("message"),
498 init, rv);
499 if (NS_WARN_IF(rv.Failed())) {
500 rv.SuppressException();
501 return false;
504 extendableEvent->SetTrusted(true);
506 return NS_SUCCEEDED(DispatchExtendableEventOnWorkerScope(
507 aCx, aWorkerPrivate->GlobalScope(), extendableEvent, nullptr));
511 } // anonymous namespace
513 nsresult ServiceWorkerPrivate::SendMessageEvent(
514 RefPtr<ServiceWorkerCloneData>&& aData,
515 const ClientInfoAndState& aClientInfoAndState) {
516 MOZ_ASSERT(NS_IsMainThread());
518 nsresult rv = SpawnWorkerIfNeeded(MessageEvent);
519 NS_ENSURE_SUCCESS(rv, rv);
521 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
522 RefPtr<SendMessageEventRunnable> runnable = new SendMessageEventRunnable(
523 mWorkerPrivate, token, aClientInfoAndState, std::move(aData));
525 if (!runnable->Dispatch()) {
526 return NS_ERROR_FAILURE;
529 return NS_OK;
532 namespace {
534 // Handle functional event
535 // 9.9.7 If the time difference in seconds calculated by the current time minus
536 // registration's last update check time is greater than 86400, invoke Soft
537 // Update algorithm.
538 class ExtendableFunctionalEventWorkerRunnable
539 : public ExtendableEventWorkerRunnable {
540 protected:
541 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
543 public:
544 ExtendableFunctionalEventWorkerRunnable(
545 WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken,
546 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
547 : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken),
548 mRegistration(aRegistration) {
549 MOZ_DIAGNOSTIC_ASSERT(aRegistration);
552 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
553 bool aRunResult) override {
554 // Sub-class PreRun() or WorkerRun() methods could clear our mRegistration.
555 if (mRegistration) {
556 nsCOMPtr<nsIRunnable> runnable =
557 new RegistrationUpdateRunnable(mRegistration, true /* time check */);
558 aWorkerPrivate->DispatchToMainThread(runnable.forget());
561 ExtendableEventWorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
566 * Fires 'install' event on the ServiceWorkerGlobalScope. Modifies busy count
567 * since it fires the event. This is ok since there can't be nested
568 * ServiceWorkers, so the parent thread -> worker thread requirement for
569 * runnables is satisfied.
571 class LifecycleEventWorkerRunnable : public ExtendableEventWorkerRunnable {
572 nsString mEventName;
573 RefPtr<LifeCycleEventCallback> mCallback;
575 public:
576 LifecycleEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
577 KeepAliveToken* aToken,
578 const nsAString& aEventName,
579 LifeCycleEventCallback* aCallback)
580 : ExtendableEventWorkerRunnable(aWorkerPrivate, aToken),
581 mEventName(aEventName),
582 mCallback(aCallback) {
583 MOZ_ASSERT(NS_IsMainThread());
586 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
587 MOZ_ASSERT(aWorkerPrivate);
588 return DispatchLifecycleEvent(aCx, aWorkerPrivate);
591 nsresult Cancel() override {
592 mCallback->SetResult(false);
593 MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(mCallback));
595 return WorkerRunnable::Cancel();
598 private:
599 bool DispatchLifecycleEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
603 * Used to handle ExtendableEvent::waitUntil() and catch abnormal worker
604 * termination during the execution of life cycle events. It is responsible
605 * with advancing the job queue for install/activate tasks.
607 class LifeCycleEventWatcher final : public ExtendableEventCallback {
608 RefPtr<StrongWorkerRef> mWorkerRef;
609 RefPtr<LifeCycleEventCallback> mCallback;
611 ~LifeCycleEventWatcher() {
612 // XXXcatalinb: If all the promises passed to waitUntil go out of scope,
613 // the resulting Promise.all will be cycle collected and it will drop its
614 // native handlers (including this object). Instead of waiting for a timeout
615 // we report the failure now.
616 ReportResult(false);
619 public:
620 NS_INLINE_DECL_REFCOUNTING(LifeCycleEventWatcher, override)
622 explicit LifeCycleEventWatcher(LifeCycleEventCallback* aCallback)
623 : mCallback(aCallback) {
624 MOZ_ASSERT(IsCurrentThreadRunningWorker());
627 bool Init() {
628 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
629 MOZ_ASSERT(workerPrivate);
631 // We need to listen for worker termination in case the event handler
632 // never completes or never resolves the waitUntil promise. There are
633 // two possible scenarios:
634 // 1. The keepAlive token expires and the worker is terminated, in which
635 // case the registration/update promise will be rejected
636 // 2. A new service worker is registered which will terminate the current
637 // installing worker.
638 RefPtr<LifeCycleEventWatcher> self = this;
639 mWorkerRef =
640 StrongWorkerRef::Create(workerPrivate, "LifeCycleEventWatcher",
641 [self]() { self->ReportResult(false); });
642 if (NS_WARN_IF(!mWorkerRef)) {
643 mCallback->SetResult(false);
644 // Using DispatchToMainThreadForMessaging so that state update on
645 // the main thread doesn't happen too soon.
646 nsresult rv = workerPrivate->DispatchToMainThreadForMessaging(mCallback);
647 Unused << NS_WARN_IF(NS_FAILED(rv));
648 return false;
651 return true;
654 void ReportResult(bool aResult) {
655 MOZ_ASSERT(IsCurrentThreadRunningWorker());
657 if (!mWorkerRef) {
658 return;
661 mCallback->SetResult(aResult);
662 // Using DispatchToMainThreadForMessaging so that state update on
663 // the main thread doesn't happen too soon.
664 nsresult rv =
665 mWorkerRef->Private()->DispatchToMainThreadForMessaging(mCallback);
666 if (NS_WARN_IF(NS_FAILED(rv))) {
667 MOZ_CRASH("Failed to dispatch life cycle event handler.");
670 mWorkerRef = nullptr;
673 void FinishedWithResult(ExtendableEventResult aResult) override {
674 MOZ_ASSERT(IsCurrentThreadRunningWorker());
675 ReportResult(aResult == Resolved);
677 // Note, all WaitUntil() rejections are reported to client consoles
678 // by the WaitUntilHandler in ServiceWorkerEvents. This ensures that
679 // errors in non-lifecycle events like FetchEvent and PushEvent are
680 // reported properly.
684 bool LifecycleEventWorkerRunnable::DispatchLifecycleEvent(
685 JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
686 aWorkerPrivate->AssertIsOnWorkerThread();
687 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
689 RefPtr<ExtendableEvent> event;
690 RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
692 if (mEventName.EqualsASCII("install") || mEventName.EqualsASCII("activate")) {
693 ExtendableEventInit init;
694 init.mBubbles = false;
695 init.mCancelable = false;
696 event = ExtendableEvent::Constructor(target, mEventName, init);
697 } else {
698 MOZ_CRASH("Unexpected lifecycle event");
701 event->SetTrusted(true);
703 // It is important to initialize the watcher before actually dispatching
704 // the event in order to catch worker termination while the event handler
705 // is still executing. This can happen with infinite loops, for example.
706 RefPtr<LifeCycleEventWatcher> watcher = new LifeCycleEventWatcher(mCallback);
708 if (!watcher->Init()) {
709 return true;
712 nsresult rv = DispatchExtendableEventOnWorkerScope(
713 aCx, aWorkerPrivate->GlobalScope(), event, watcher);
714 // Do not fail event processing when an exception is thrown.
715 if (NS_FAILED(rv) && rv != NS_ERROR_XPC_JS_THREW_EXCEPTION) {
716 watcher->ReportResult(false);
719 return true;
722 } // anonymous namespace
724 nsresult ServiceWorkerPrivate::SendLifeCycleEvent(
725 const nsAString& aEventType, LifeCycleEventCallback* aCallback) {
726 nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent);
727 NS_ENSURE_SUCCESS(rv, rv);
729 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
730 RefPtr<WorkerRunnable> r = new LifecycleEventWorkerRunnable(
731 mWorkerPrivate, token, aEventType, aCallback);
732 if (NS_WARN_IF(!r->Dispatch())) {
733 return NS_ERROR_FAILURE;
736 return NS_OK;
739 namespace {
741 class PushErrorReporter final : public ExtendableEventCallback {
742 WorkerPrivate* mWorkerPrivate;
743 nsString mMessageId;
745 ~PushErrorReporter() {}
747 public:
748 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PushErrorReporter, override)
750 PushErrorReporter(WorkerPrivate* aWorkerPrivate, const nsAString& aMessageId)
751 : mWorkerPrivate(aWorkerPrivate), mMessageId(aMessageId) {
752 mWorkerPrivate->AssertIsOnWorkerThread();
755 void FinishedWithResult(ExtendableEventResult aResult) override {
756 if (aResult == Rejected) {
757 Report(nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
761 void Report(
762 uint16_t aReason = nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) {
763 WorkerPrivate* workerPrivate = mWorkerPrivate;
764 mWorkerPrivate->AssertIsOnWorkerThread();
766 if (NS_WARN_IF(aReason > nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) ||
767 mMessageId.IsEmpty()) {
768 return;
770 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<uint16_t>(
771 "dom::PushErrorReporter::ReportOnMainThread", this,
772 &PushErrorReporter::ReportOnMainThread, aReason);
773 MOZ_ALWAYS_TRUE(
774 NS_SUCCEEDED(workerPrivate->DispatchToMainThread(runnable.forget())));
777 void ReportOnMainThread(uint16_t aReason) {
778 MOZ_ASSERT(NS_IsMainThread());
779 nsCOMPtr<nsIPushErrorReporter> reporter =
780 do_GetService("@mozilla.org/push/Service;1");
781 if (reporter) {
782 nsresult rv = reporter->ReportDeliveryError(mMessageId, aReason);
783 Unused << NS_WARN_IF(NS_FAILED(rv));
788 class SendPushEventRunnable final
789 : public ExtendableFunctionalEventWorkerRunnable {
790 nsString mMessageId;
791 Maybe<nsTArray<uint8_t>> mData;
793 public:
794 SendPushEventRunnable(
795 WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken,
796 const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData,
797 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
798 : ExtendableFunctionalEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken,
799 aRegistration),
800 mMessageId(aMessageId),
801 mData(aData) {
802 MOZ_ASSERT(NS_IsMainThread());
803 MOZ_ASSERT(aWorkerPrivate);
804 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
807 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
808 MOZ_ASSERT(aWorkerPrivate);
809 GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
811 RefPtr<PushErrorReporter> errorReporter =
812 new PushErrorReporter(aWorkerPrivate, mMessageId);
814 PushEventInit pei;
815 if (mData) {
816 const nsTArray<uint8_t>& bytes = mData.ref();
817 JSObject* data =
818 Uint8Array::Create(aCx, bytes.Length(), bytes.Elements());
819 if (!data) {
820 errorReporter->Report();
821 return false;
823 pei.mData.Construct().SetAsArrayBufferView().Init(data);
825 pei.mBubbles = false;
826 pei.mCancelable = false;
828 ErrorResult result;
829 RefPtr<PushEvent> event = PushEvent::Constructor(
830 globalObj, NS_LITERAL_STRING("push"), pei, result);
831 if (NS_WARN_IF(result.Failed())) {
832 result.SuppressException();
833 errorReporter->Report();
834 return false;
836 event->SetTrusted(true);
838 nsresult rv = DispatchExtendableEventOnWorkerScope(
839 aCx, aWorkerPrivate->GlobalScope(), event, errorReporter);
840 if (NS_FAILED(rv)) {
841 // We don't cancel WorkerPrivate when catching an excetpion.
842 errorReporter->Report(nsIPushErrorReporter::DELIVERY_UNCAUGHT_EXCEPTION);
845 return true;
849 class SendPushSubscriptionChangeEventRunnable final
850 : public ExtendableEventWorkerRunnable {
851 public:
852 explicit SendPushSubscriptionChangeEventRunnable(
853 WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken)
854 : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken) {
855 MOZ_ASSERT(NS_IsMainThread());
856 MOZ_ASSERT(aWorkerPrivate);
857 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
860 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
861 MOZ_ASSERT(aWorkerPrivate);
863 RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
865 ExtendableEventInit init;
866 init.mBubbles = false;
867 init.mCancelable = false;
869 RefPtr<ExtendableEvent> event = ExtendableEvent::Constructor(
870 target, NS_LITERAL_STRING("pushsubscriptionchange"), init);
872 event->SetTrusted(true);
874 DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
875 event, nullptr);
877 return true;
881 } // anonymous namespace
883 nsresult ServiceWorkerPrivate::SendPushEvent(
884 const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData,
885 ServiceWorkerRegistrationInfo* aRegistration) {
886 nsresult rv = SpawnWorkerIfNeeded(PushEvent);
887 NS_ENSURE_SUCCESS(rv, rv);
889 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
891 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
892 new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
893 "ServiceWorkerRegistrationInfoProxy", aRegistration, false));
895 RefPtr<WorkerRunnable> r = new SendPushEventRunnable(
896 mWorkerPrivate, token, aMessageId, aData, regInfo);
898 if (mInfo->State() == ServiceWorkerState::Activating) {
899 mPendingFunctionalEvents.AppendElement(r.forget());
900 return NS_OK;
903 MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
905 if (NS_WARN_IF(!r->Dispatch())) {
906 return NS_ERROR_FAILURE;
909 return NS_OK;
912 nsresult ServiceWorkerPrivate::SendPushSubscriptionChangeEvent() {
913 nsresult rv = SpawnWorkerIfNeeded(PushSubscriptionChangeEvent);
914 NS_ENSURE_SUCCESS(rv, rv);
916 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
917 RefPtr<WorkerRunnable> r =
918 new SendPushSubscriptionChangeEventRunnable(mWorkerPrivate, token);
919 if (NS_WARN_IF(!r->Dispatch())) {
920 return NS_ERROR_FAILURE;
923 return NS_OK;
926 namespace {
928 class AllowWindowInteractionHandler final : public ExtendableEventCallback,
929 public nsITimerCallback,
930 public nsINamed {
931 nsCOMPtr<nsITimer> mTimer;
932 RefPtr<StrongWorkerRef> mWorkerRef;
934 ~AllowWindowInteractionHandler() {
935 // We must either fail to initialize or call ClearWindowAllowed.
936 MOZ_DIAGNOSTIC_ASSERT(!mTimer);
937 MOZ_DIAGNOSTIC_ASSERT(!mWorkerRef);
940 void ClearWindowAllowed(WorkerPrivate* aWorkerPrivate) {
941 MOZ_ASSERT(aWorkerPrivate);
942 aWorkerPrivate->AssertIsOnWorkerThread();
944 if (!mTimer) {
945 return;
948 // XXXcatalinb: This *might* be executed after the global was unrooted, in
949 // which case GlobalScope() will return null. Making the check here just
950 // to be safe.
951 WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
952 if (!globalScope) {
953 return;
956 globalScope->ConsumeWindowInteraction();
957 mTimer->Cancel();
958 mTimer = nullptr;
960 mWorkerRef = nullptr;
963 void StartClearWindowTimer(WorkerPrivate* aWorkerPrivate) {
964 MOZ_ASSERT(aWorkerPrivate);
965 aWorkerPrivate->AssertIsOnWorkerThread();
966 MOZ_ASSERT(!mTimer);
968 nsresult rv;
969 nsCOMPtr<nsITimer> timer =
970 NS_NewTimer(aWorkerPrivate->ControlEventTarget());
971 if (NS_WARN_IF(!timer)) {
972 return;
975 MOZ_ASSERT(!mWorkerRef);
976 RefPtr<AllowWindowInteractionHandler> self = this;
977 mWorkerRef = StrongWorkerRef::Create(
978 aWorkerPrivate, "AllowWindowInteractionHandler", [self]() {
979 // We could try to hold the worker alive until the timer fires, but
980 // other APIs are not likely to work in this partially shutdown state.
981 // We might as well let the worker thread exit.
982 self->ClearWindowAllowed(self->mWorkerRef->Private());
985 if (!mWorkerRef) {
986 return;
989 aWorkerPrivate->GlobalScope()->AllowWindowInteraction();
990 timer.swap(mTimer);
992 // We swap first and then initialize the timer so that even if initializing
993 // fails, we still clean the busy count and interaction count correctly.
994 // The timer can't be initialized before modifying the busy count since the
995 // timer thread could run and call the timeout but the worker may
996 // already be terminating and modifying the busy count could fail.
997 rv = mTimer->InitWithCallback(this, gDOMDisableOpenClickDelay,
998 nsITimer::TYPE_ONE_SHOT);
999 if (NS_WARN_IF(NS_FAILED(rv))) {
1000 ClearWindowAllowed(aWorkerPrivate);
1001 return;
1005 // nsITimerCallback virtual methods
1006 NS_IMETHOD
1007 Notify(nsITimer* aTimer) override {
1008 MOZ_DIAGNOSTIC_ASSERT(mTimer == aTimer);
1009 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1010 ClearWindowAllowed(workerPrivate);
1011 return NS_OK;
1014 // nsINamed virtual methods
1015 NS_IMETHOD
1016 GetName(nsACString& aName) override {
1017 aName.AssignLiteral("AllowWindowInteractionHandler");
1018 return NS_OK;
1021 public:
1022 NS_DECL_THREADSAFE_ISUPPORTS
1024 explicit AllowWindowInteractionHandler(WorkerPrivate* aWorkerPrivate) {
1025 StartClearWindowTimer(aWorkerPrivate);
1028 void FinishedWithResult(ExtendableEventResult /* aResult */) override {
1029 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1030 ClearWindowAllowed(workerPrivate);
1034 NS_IMPL_ISUPPORTS(AllowWindowInteractionHandler, nsITimerCallback, nsINamed)
1036 class SendNotificationEventRunnable final
1037 : public ExtendableEventWorkerRunnable {
1038 const nsString mEventName;
1039 const nsString mID;
1040 const nsString mTitle;
1041 const nsString mDir;
1042 const nsString mLang;
1043 const nsString mBody;
1044 const nsString mTag;
1045 const nsString mIcon;
1046 const nsString mData;
1047 const nsString mBehavior;
1048 const nsString mScope;
1050 public:
1051 SendNotificationEventRunnable(WorkerPrivate* aWorkerPrivate,
1052 KeepAliveToken* aKeepAliveToken,
1053 const nsAString& aEventName,
1054 const nsAString& aID, const nsAString& aTitle,
1055 const nsAString& aDir, const nsAString& aLang,
1056 const nsAString& aBody, const nsAString& aTag,
1057 const nsAString& aIcon, const nsAString& aData,
1058 const nsAString& aBehavior,
1059 const nsAString& aScope)
1060 : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken),
1061 mEventName(aEventName),
1062 mID(aID),
1063 mTitle(aTitle),
1064 mDir(aDir),
1065 mLang(aLang),
1066 mBody(aBody),
1067 mTag(aTag),
1068 mIcon(aIcon),
1069 mData(aData),
1070 mBehavior(aBehavior),
1071 mScope(aScope) {
1072 MOZ_ASSERT(NS_IsMainThread());
1073 MOZ_ASSERT(aWorkerPrivate);
1074 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
1077 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1078 MOZ_ASSERT(aWorkerPrivate);
1080 RefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());
1082 ErrorResult result;
1083 RefPtr<Notification> notification = Notification::ConstructFromFields(
1084 aWorkerPrivate->GlobalScope(), mID, mTitle, mDir, mLang, mBody, mTag,
1085 mIcon, mData, mScope, result);
1086 if (NS_WARN_IF(result.Failed())) {
1087 return false;
1090 NotificationEventInit nei;
1091 nei.mNotification = notification;
1092 nei.mBubbles = false;
1093 nei.mCancelable = false;
1095 RefPtr<NotificationEvent> event =
1096 NotificationEvent::Constructor(target, mEventName, nei, result);
1097 if (NS_WARN_IF(result.Failed())) {
1098 return false;
1101 event->SetTrusted(true);
1103 RefPtr<AllowWindowInteractionHandler> allowWindowInteraction;
1104 if (mEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
1105 allowWindowInteraction =
1106 new AllowWindowInteractionHandler(aWorkerPrivate);
1109 nsresult rv = DispatchExtendableEventOnWorkerScope(
1110 aCx, aWorkerPrivate->GlobalScope(), event, allowWindowInteraction);
1111 // Don't reject when catching an exception
1112 if (NS_FAILED(rv) && rv != NS_ERROR_XPC_JS_THREW_EXCEPTION &&
1113 allowWindowInteraction) {
1114 allowWindowInteraction->FinishedWithResult(Rejected);
1117 return true;
1121 } // namespace
1123 nsresult ServiceWorkerPrivate::SendNotificationEvent(
1124 const nsAString& aEventName, const nsAString& aID, const nsAString& aTitle,
1125 const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
1126 const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
1127 const nsAString& aBehavior, const nsAString& aScope) {
1128 WakeUpReason why;
1129 if (aEventName.EqualsLiteral(NOTIFICATION_CLICK_EVENT_NAME)) {
1130 why = NotificationClickEvent;
1131 gDOMDisableOpenClickDelay =
1132 Preferences::GetInt("dom.serviceWorkers.disable_open_click_delay");
1133 } else if (aEventName.EqualsLiteral(NOTIFICATION_CLOSE_EVENT_NAME)) {
1134 why = NotificationCloseEvent;
1135 } else {
1136 MOZ_ASSERT_UNREACHABLE("Invalid notification event name");
1137 return NS_ERROR_FAILURE;
1140 nsresult rv = SpawnWorkerIfNeeded(why);
1141 NS_ENSURE_SUCCESS(rv, rv);
1143 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
1145 RefPtr<WorkerRunnable> r = new SendNotificationEventRunnable(
1146 mWorkerPrivate, token, aEventName, aID, aTitle, aDir, aLang, aBody, aTag,
1147 aIcon, aData, aBehavior, aScope);
1148 if (NS_WARN_IF(!r->Dispatch())) {
1149 return NS_ERROR_FAILURE;
1152 return NS_OK;
1155 namespace {
1157 // Inheriting ExtendableEventWorkerRunnable so that the worker is not terminated
1158 // while handling the fetch event, though that's very unlikely.
1159 class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable,
1160 public nsIHttpHeaderVisitor {
1161 nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
1162 const nsCString mScriptSpec;
1163 nsTArray<nsCString> mHeaderNames;
1164 nsTArray<nsCString> mHeaderValues;
1165 nsCString mSpec;
1166 nsCString mFragment;
1167 nsCString mMethod;
1168 nsString mClientId;
1169 nsString mResultingClientId;
1170 bool mIsReload;
1171 bool mMarkLaunchServiceWorkerEnd;
1172 RequestCache mCacheMode;
1173 RequestMode mRequestMode;
1174 RequestRedirect mRequestRedirect;
1175 RequestCredentials mRequestCredentials;
1176 nsContentPolicyType mContentPolicyType;
1177 nsCOMPtr<nsIInputStream> mUploadStream;
1178 int64_t mUploadStreamContentLength;
1179 nsCString mReferrer;
1180 ReferrerPolicy mReferrerPolicy;
1181 nsString mIntegrity;
1182 const bool mIsNonSubresourceRequest;
1184 public:
1185 FetchEventRunnable(
1186 WorkerPrivate* aWorkerPrivate, KeepAliveToken* aKeepAliveToken,
1187 nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
1188 // CSP checks might require the worker script spec
1189 // later on.
1190 const nsACString& aScriptSpec,
1191 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
1192 const nsAString& aClientId, const nsAString& aResultingClientId,
1193 bool aIsReload, bool aMarkLaunchServiceWorkerEnd,
1194 bool aIsNonSubresourceRequest)
1195 : ExtendableFunctionalEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken,
1196 aRegistration),
1197 mInterceptedChannel(aChannel),
1198 mScriptSpec(aScriptSpec),
1199 mClientId(aClientId),
1200 mResultingClientId(aResultingClientId),
1201 mIsReload(aIsReload),
1202 mMarkLaunchServiceWorkerEnd(aMarkLaunchServiceWorkerEnd),
1203 mCacheMode(RequestCache::Default),
1204 mRequestMode(RequestMode::No_cors),
1205 mRequestRedirect(RequestRedirect::Follow)
1206 // By default we set it to same-origin since normal HTTP fetches always
1207 // send credentials to same-origin websites unless explicitly forbidden.
1209 mRequestCredentials(RequestCredentials::Same_origin),
1210 mContentPolicyType(nsIContentPolicy::TYPE_INVALID),
1211 mUploadStreamContentLength(-1),
1212 mReferrer(kFETCH_CLIENT_REFERRER_STR),
1213 mReferrerPolicy(ReferrerPolicy::_empty),
1214 mIsNonSubresourceRequest(aIsNonSubresourceRequest) {
1215 MOZ_ASSERT(aWorkerPrivate);
1218 NS_DECL_ISUPPORTS_INHERITED
1220 NS_IMETHOD
1221 VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
1222 mHeaderNames.AppendElement(aHeader);
1223 mHeaderValues.AppendElement(aValue);
1224 return NS_OK;
1227 nsresult Init() {
1228 MOZ_ASSERT(NS_IsMainThread());
1229 nsCOMPtr<nsIChannel> channel;
1230 nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
1231 NS_ENSURE_SUCCESS(rv, rv);
1233 nsCOMPtr<nsIURI> uri;
1234 rv = mInterceptedChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
1235 NS_ENSURE_SUCCESS(rv, rv);
1237 // Normally we rely on the Request constructor to strip the fragment, but
1238 // when creating the FetchEvent we bypass the constructor. So strip the
1239 // fragment manually here instead. We can't do it later when we create
1240 // the Request because that code executes off the main thread.
1241 nsCOMPtr<nsIURI> uriNoFragment;
1242 rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment));
1243 NS_ENSURE_SUCCESS(rv, rv);
1244 rv = uriNoFragment->GetSpec(mSpec);
1245 NS_ENSURE_SUCCESS(rv, rv);
1246 rv = uri->GetRef(mFragment);
1247 NS_ENSURE_SUCCESS(rv, rv);
1249 uint32_t loadFlags;
1250 rv = channel->GetLoadFlags(&loadFlags);
1251 NS_ENSURE_SUCCESS(rv, rv);
1252 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1253 mContentPolicyType = loadInfo->InternalContentPolicyType();
1255 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
1256 MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
1258 mReferrer = EmptyCString();
1259 uint32_t referrerPolicy = 0;
1260 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
1261 if (referrerInfo) {
1262 referrerPolicy = referrerInfo->GetReferrerPolicy();
1263 nsCOMPtr<nsIURI> computedReferrer = referrerInfo->GetComputedReferrer();
1264 if (computedReferrer) {
1265 rv = computedReferrer->GetSpec(mReferrer);
1266 NS_ENSURE_SUCCESS(rv, rv);
1269 switch (referrerPolicy) {
1270 case nsIHttpChannel::REFERRER_POLICY_UNSET:
1271 mReferrerPolicy = ReferrerPolicy::_empty;
1272 break;
1273 case nsIHttpChannel::REFERRER_POLICY_NO_REFERRER:
1274 mReferrerPolicy = ReferrerPolicy::No_referrer;
1275 break;
1276 case nsIHttpChannel::REFERRER_POLICY_ORIGIN:
1277 mReferrerPolicy = ReferrerPolicy::Origin;
1278 break;
1279 case nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE:
1280 mReferrerPolicy = ReferrerPolicy::No_referrer_when_downgrade;
1281 break;
1282 case nsIHttpChannel::REFERRER_POLICY_ORIGIN_WHEN_XORIGIN:
1283 mReferrerPolicy = ReferrerPolicy::Origin_when_cross_origin;
1284 break;
1285 case nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL:
1286 mReferrerPolicy = ReferrerPolicy::Unsafe_url;
1287 break;
1288 case nsIHttpChannel::REFERRER_POLICY_SAME_ORIGIN:
1289 mReferrerPolicy = ReferrerPolicy::Same_origin;
1290 break;
1291 case nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN:
1292 mReferrerPolicy = ReferrerPolicy::Strict_origin_when_cross_origin;
1293 break;
1294 case nsIHttpChannel::REFERRER_POLICY_STRICT_ORIGIN:
1295 mReferrerPolicy = ReferrerPolicy::Strict_origin;
1296 break;
1297 default:
1298 MOZ_ASSERT_UNREACHABLE("Invalid Referrer Policy enum value?");
1299 break;
1302 rv = httpChannel->GetRequestMethod(mMethod);
1303 NS_ENSURE_SUCCESS(rv, rv);
1305 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
1306 do_QueryInterface(httpChannel);
1307 NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
1309 mRequestMode = InternalRequest::MapChannelToRequestMode(channel);
1311 // This is safe due to static_asserts in ServiceWorkerManager.cpp.
1312 uint32_t redirectMode;
1313 rv = internalChannel->GetRedirectMode(&redirectMode);
1314 MOZ_ASSERT(NS_SUCCEEDED(rv));
1315 mRequestRedirect = static_cast<RequestRedirect>(redirectMode);
1317 // This is safe due to static_asserts in ServiceWorkerManager.cpp.
1318 uint32_t cacheMode;
1319 rv = internalChannel->GetFetchCacheMode(&cacheMode);
1320 MOZ_ASSERT(NS_SUCCEEDED(rv));
1321 mCacheMode = static_cast<RequestCache>(cacheMode);
1323 rv = internalChannel->GetIntegrityMetadata(mIntegrity);
1324 MOZ_ASSERT(NS_SUCCEEDED(rv));
1326 mRequestCredentials =
1327 InternalRequest::MapChannelToRequestCredentials(channel);
1329 rv = httpChannel->VisitNonDefaultRequestHeaders(this);
1330 NS_ENSURE_SUCCESS(rv, rv);
1332 nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
1333 if (uploadChannel) {
1334 MOZ_ASSERT(!mUploadStream);
1335 nsCOMPtr<nsIInputStream> uploadStream;
1336 rv = uploadChannel->CloneUploadStream(&mUploadStreamContentLength,
1337 getter_AddRefs(uploadStream));
1338 NS_ENSURE_SUCCESS(rv, rv);
1339 mUploadStream = uploadStream;
1342 return NS_OK;
1345 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1346 MOZ_ASSERT(aWorkerPrivate);
1348 if (mMarkLaunchServiceWorkerEnd) {
1349 mInterceptedChannel->SetLaunchServiceWorkerEnd(TimeStamp::Now());
1351 // A probe to measure sw launch time for telemetry.
1352 TimeStamp launchStartTime = TimeStamp();
1353 mInterceptedChannel->GetLaunchServiceWorkerStart(&launchStartTime);
1355 TimeStamp launchEndTime = TimeStamp();
1356 mInterceptedChannel->GetLaunchServiceWorkerEnd(&launchEndTime);
1357 Telemetry::AccumulateTimeDelta(Telemetry::SERVICE_WORKER_LAUNCH_TIME,
1358 launchStartTime, launchEndTime);
1361 mInterceptedChannel->SetDispatchFetchEventEnd(TimeStamp::Now());
1362 return DispatchFetchEvent(aCx, aWorkerPrivate);
1365 nsresult Cancel() override {
1366 nsCOMPtr<nsIRunnable> runnable = new ResumeRequest(mInterceptedChannel);
1367 if (NS_FAILED(mWorkerPrivate->DispatchToMainThread(runnable))) {
1368 NS_WARNING("Failed to resume channel on FetchEventRunnable::Cancel()!\n");
1370 WorkerRunnable::Cancel();
1371 return NS_OK;
1374 private:
1375 ~FetchEventRunnable() {}
1377 class ResumeRequest final : public Runnable {
1378 nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
1380 public:
1381 explicit ResumeRequest(
1382 nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
1383 : Runnable("dom::FetchEventRunnable::ResumeRequest"),
1384 mChannel(aChannel) {
1385 mChannel->SetFinishResponseStart(TimeStamp::Now());
1388 NS_IMETHOD Run() override {
1389 MOZ_ASSERT(NS_IsMainThread());
1391 TimeStamp timeStamp = TimeStamp::Now();
1392 mChannel->SetHandleFetchEventEnd(timeStamp);
1393 mChannel->SetChannelResetEnd(timeStamp);
1394 mChannel->SaveTimeStamps();
1396 nsresult rv = mChannel->ResetInterception();
1397 if (NS_FAILED(rv)) {
1398 NS_WARNING("Failed to resume intercepted network request");
1399 mChannel->CancelInterception(rv);
1401 return rv;
1405 bool DispatchFetchEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
1406 MOZ_ASSERT(aCx);
1407 MOZ_ASSERT(aWorkerPrivate);
1408 MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
1409 GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
1411 RefPtr<InternalHeaders> internalHeaders =
1412 new InternalHeaders(HeadersGuardEnum::Request);
1413 MOZ_ASSERT(mHeaderNames.Length() == mHeaderValues.Length());
1414 for (uint32_t i = 0; i < mHeaderNames.Length(); i++) {
1415 ErrorResult result;
1416 internalHeaders->Set(mHeaderNames[i], mHeaderValues[i], result);
1417 if (NS_WARN_IF(result.Failed())) {
1418 result.SuppressException();
1419 return false;
1423 ErrorResult result;
1424 internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result);
1425 if (NS_WARN_IF(result.Failed())) {
1426 result.SuppressException();
1427 return false;
1429 RefPtr<InternalRequest> internalReq = new InternalRequest(
1430 mSpec, mFragment, mMethod, internalHeaders.forget(), mCacheMode,
1431 mRequestMode, mRequestRedirect, mRequestCredentials,
1432 NS_ConvertUTF8toUTF16(mReferrer), mReferrerPolicy, mContentPolicyType,
1433 mIntegrity);
1434 internalReq->SetBody(mUploadStream, mUploadStreamContentLength);
1435 // For Telemetry, note that this Request object was created by a Fetch
1436 // event.
1437 internalReq->SetCreatedByFetchEvent();
1439 nsCOMPtr<nsIChannel> channel;
1440 nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
1441 NS_ENSURE_SUCCESS(rv, false);
1443 nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(channel);
1444 if (cic && !cic->PreferredAlternativeDataTypes().IsEmpty()) {
1445 // TODO: the internal request probably needs all the preferred types.
1446 nsAutoCString alternativeDataType;
1447 alternativeDataType.Assign(
1448 cic->PreferredAlternativeDataTypes()[0].type());
1449 internalReq->SetPreferredAlternativeDataType(alternativeDataType);
1452 nsCOMPtr<nsIGlobalObject> global =
1453 do_QueryInterface(globalObj.GetAsSupports());
1454 if (NS_WARN_IF(!global)) {
1455 return false;
1458 // TODO This request object should be created with a AbortSignal object
1459 // which should be aborted if the loading is aborted. See bug 1394102.
1460 RefPtr<Request> request = new Request(global, internalReq, nullptr);
1462 MOZ_ASSERT_IF(internalReq->IsNavigationRequest(),
1463 request->Redirect() == RequestRedirect::Manual);
1465 RootedDictionary<FetchEventInit> init(aCx);
1466 init.mRequest = request;
1467 init.mBubbles = false;
1468 init.mCancelable = true;
1469 // Only expose the FetchEvent.clientId on subresource requests for now.
1470 // Once we implement .targetClientId we can then start exposing .clientId
1471 // on non-subresource requests as well. See bug 1487534.
1472 if (!mClientId.IsEmpty() && !internalReq->IsNavigationRequest()) {
1473 init.mClientId = mClientId;
1477 * https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm
1479 * "If request is a non-subresource request and request’s
1480 * destination is not "report", initialize e’s resultingClientId attribute
1481 * to reservedClient’s [resultingClient's] id, and to the empty string
1482 * otherwise." (Step 18.8)
1484 if (!mResultingClientId.IsEmpty() && mIsNonSubresourceRequest &&
1485 internalReq->Destination() != RequestDestination::Report) {
1486 init.mResultingClientId = mResultingClientId;
1489 init.mIsReload = mIsReload;
1490 RefPtr<FetchEvent> event = FetchEvent::Constructor(
1491 globalObj, NS_LITERAL_STRING("fetch"), init, result);
1492 if (NS_WARN_IF(result.Failed())) {
1493 result.SuppressException();
1494 return false;
1497 event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec);
1498 event->SetTrusted(true);
1500 mInterceptedChannel->SetHandleFetchEventStart(TimeStamp::Now());
1502 nsresult rv2 = DispatchExtendableEventOnWorkerScope(
1503 aCx, aWorkerPrivate->GlobalScope(), event, nullptr);
1504 if ((NS_WARN_IF(NS_FAILED(rv2)) &&
1505 rv2 != NS_ERROR_XPC_JS_THREW_EXCEPTION) ||
1506 !event->WaitToRespond()) {
1507 nsCOMPtr<nsIRunnable> runnable;
1508 MOZ_ASSERT(!aWorkerPrivate->UsesSystemPrincipal(),
1509 "We don't support system-principal serviceworkers");
1510 if (event->DefaultPrevented(CallerType::NonSystem)) {
1511 runnable = new CancelChannelRunnable(mInterceptedChannel, mRegistration,
1512 NS_ERROR_INTERCEPTION_FAILED);
1513 } else {
1514 runnable = new ResumeRequest(mInterceptedChannel);
1517 MOZ_ALWAYS_SUCCEEDS(
1518 mWorkerPrivate->DispatchToMainThread(runnable.forget()));
1521 return true;
1525 NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable,
1526 nsIHttpHeaderVisitor)
1528 } // anonymous namespace
1530 nsresult ServiceWorkerPrivate::SendFetchEvent(
1531 nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup,
1532 const nsAString& aClientId, const nsAString& aResultingClientId,
1533 bool aIsReload) {
1534 MOZ_ASSERT(NS_IsMainThread());
1536 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1537 if (NS_WARN_IF(!mInfo || !swm)) {
1538 return NS_ERROR_FAILURE;
1541 RefPtr<ServiceWorkerRegistrationInfo> registration =
1542 swm->GetRegistration(mInfo->Principal(), mInfo->Scope());
1544 // Its possible the registration is removed between starting the interception
1545 // and actually dispatching the fetch event. In these cases we simply
1546 // want to restart the original network request. Since this is a normal
1547 // condition we handle the reset here instead of returning an error which
1548 // would in turn trigger a console report.
1549 if (!registration) {
1550 nsresult rv = aChannel->ResetInterception();
1551 if (NS_FAILED(rv)) {
1552 NS_WARNING("Failed to resume intercepted network request");
1553 aChannel->CancelInterception(rv);
1555 return NS_OK;
1558 // Handle Fetch algorithm - step 16. If the service worker didn't register
1559 // any fetch event handlers, then abort the interception and maybe trigger
1560 // the soft update algorithm.
1561 if (!mInfo->HandlesFetch()) {
1562 nsresult rv = aChannel->ResetInterception();
1563 if (NS_FAILED(rv)) {
1564 NS_WARNING("Failed to resume intercepted network request");
1565 aChannel->CancelInterception(rv);
1568 // Trigger soft updates if necessary.
1569 registration->MaybeScheduleTimeCheckAndUpdate();
1571 return NS_OK;
1574 aChannel->SetLaunchServiceWorkerStart(TimeStamp::Now());
1575 aChannel->SetDispatchFetchEventStart(TimeStamp::Now());
1577 bool newWorkerCreated = false;
1578 nsresult rv = SpawnWorkerIfNeeded(FetchEvent, &newWorkerCreated, aLoadGroup);
1579 NS_ENSURE_SUCCESS(rv, rv);
1581 if (!newWorkerCreated) {
1582 aChannel->SetLaunchServiceWorkerEnd(TimeStamp::Now());
1585 nsMainThreadPtrHandle<nsIInterceptedChannel> handle(
1586 new nsMainThreadPtrHolder<nsIInterceptedChannel>("nsIInterceptedChannel",
1587 aChannel, false));
1589 nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
1590 new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
1591 "ServiceWorkerRegistrationInfoProxy", registration, false));
1593 RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
1595 nsCOMPtr<nsIChannel> channel;
1596 rv = aChannel->GetChannel(getter_AddRefs(channel));
1597 NS_ENSURE_SUCCESS(rv, rv);
1598 bool isNonSubresourceRequest =
1599 nsContentUtils::IsNonSubresourceRequest(channel);
1601 RefPtr<FetchEventRunnable> r = new FetchEventRunnable(
1602 mWorkerPrivate, token, handle, mInfo->ScriptSpec(), regInfo, aClientId,
1603 aResultingClientId, aIsReload, newWorkerCreated, isNonSubresourceRequest);
1604 rv = r->Init();
1605 if (NS_WARN_IF(NS_FAILED(rv))) {
1606 return rv;
1609 if (mInfo->State() == ServiceWorkerState::Activating) {
1610 mPendingFunctionalEvents.AppendElement(r.forget());
1611 return NS_OK;
1614 MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
1616 if (NS_WARN_IF(!r->Dispatch())) {
1617 return NS_ERROR_FAILURE;
1620 return NS_OK;
1623 nsresult ServiceWorkerPrivate::SpawnWorkerIfNeeded(WakeUpReason aWhy,
1624 bool* aNewWorkerCreated,
1625 nsILoadGroup* aLoadGroup) {
1626 MOZ_ASSERT(NS_IsMainThread());
1628 // Defaults to no new worker created, but if there is one, we'll set the value
1629 // to true at the end of this function.
1630 if (aNewWorkerCreated) {
1631 *aNewWorkerCreated = false;
1634 // If the worker started shutting down on itself we may have a stale
1635 // reference here. Invoke our termination code to clean it out.
1636 if (mWorkerPrivate && mWorkerPrivate->ParentStatusProtected() > Running) {
1637 TerminateWorker();
1638 MOZ_DIAGNOSTIC_ASSERT(!mWorkerPrivate);
1641 if (mWorkerPrivate) {
1642 // If we have a load group here then use it to update the service worker
1643 // load group. This was added when we needed the load group's tab child
1644 // to pass some security checks. Those security checks are gone, though,
1645 // and we could possibly remove this now. For now we just do it
1646 // opportunistically. When the service worker is running in a separate
1647 // process from the client that initiated the intercepted channel, then
1648 // the load group will be nullptr. UpdateOverrideLoadGroup ignores nullptr
1649 // load groups.
1650 mWorkerPrivate->UpdateOverridenLoadGroup(aLoadGroup);
1651 RenewKeepAliveToken(aWhy);
1653 return NS_OK;
1656 // Sanity check: mSupportsArray should be empty if we're about to
1657 // spin up a new worker.
1658 MOZ_ASSERT(mSupportsArray.IsEmpty());
1660 if (NS_WARN_IF(!mInfo)) {
1661 NS_WARNING("Trying to wake up a dead service worker.");
1662 return NS_ERROR_FAILURE;
1665 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1666 NS_ENSURE_TRUE(swm, NS_ERROR_FAILURE);
1668 RefPtr<ServiceWorkerRegistrationInfo> reg =
1669 swm->GetRegistration(mInfo->Principal(), mInfo->Scope());
1670 NS_ENSURE_TRUE(reg, NS_ERROR_FAILURE);
1672 // TODO(catalinb): Bug 1192138 - Add telemetry for service worker wake-ups.
1674 // Ensure that the IndexedDatabaseManager is initialized
1675 Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
1677 WorkerLoadInfo info;
1678 nsresult rv = NS_NewURI(getter_AddRefs(info.mBaseURI), mInfo->ScriptSpec(),
1679 nullptr, nullptr);
1681 if (NS_WARN_IF(NS_FAILED(rv))) {
1682 return rv;
1685 info.mResolvedScriptURI = info.mBaseURI;
1686 MOZ_ASSERT(!mInfo->CacheName().IsEmpty());
1687 info.mServiceWorkerCacheName = mInfo->CacheName();
1689 info.mServiceWorkerDescriptor.emplace(mInfo->Descriptor());
1690 info.mServiceWorkerRegistrationDescriptor.emplace(reg->Descriptor());
1692 info.mLoadGroup = aLoadGroup;
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;
1700 rv = info.mBaseURI->GetHost(info.mDomain);
1701 if (NS_WARN_IF(NS_FAILED(rv))) {
1702 return rv;
1705 info.mPrincipal = mInfo->Principal();
1706 info.mLoadingPrincipal = info.mPrincipal;
1707 // StoragePrincipal for ServiceWorkers is equal to mPrincipal because, at the
1708 // moment, ServiceWorkers are not exposed in partitioned contexts.
1709 info.mStoragePrincipal = info.mPrincipal;
1711 info.mCookieSettings = mozilla::net::CookieSettings::Create();
1712 MOZ_ASSERT(info.mCookieSettings);
1714 info.mStorageAccess =
1715 StorageAllowedForServiceWorker(info.mPrincipal, info.mCookieSettings);
1717 info.mOriginAttributes = mInfo->GetOriginAttributes();
1719 // Verify that we don't have any CSP on pristine client.
1720 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1721 nsCOMPtr<nsIContentSecurityPolicy> csp;
1722 if (info.mChannel) {
1723 nsCOMPtr<nsILoadInfo> loadinfo = info.mChannel->LoadInfo();
1724 csp = loadinfo->GetCsp();
1726 MOZ_DIAGNOSTIC_ASSERT(!csp);
1727 #endif
1729 // Default CSP permissions for now. These will be overrided if necessary
1730 // based on the script CSP headers during load in ScriptLoader.
1731 info.mEvalAllowed = true;
1732 info.mReportCSPViolations = false;
1734 WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mPrincipal);
1736 rv = info.SetPrincipalsAndCSPOnMainThread(
1737 info.mPrincipal, info.mStoragePrincipal, info.mLoadGroup, nullptr);
1738 if (NS_WARN_IF(NS_FAILED(rv))) {
1739 return rv;
1742 AutoJSAPI jsapi;
1743 jsapi.Init();
1744 ErrorResult error;
1745 NS_ConvertUTF8toUTF16 scriptSpec(mInfo->ScriptSpec());
1747 mWorkerPrivate = WorkerPrivate::Constructor(jsapi.cx(), scriptSpec, false,
1748 WorkerTypeService, VoidString(),
1749 EmptyCString(), &info, error);
1750 if (NS_WARN_IF(error.Failed())) {
1751 return error.StealNSResult();
1754 RenewKeepAliveToken(aWhy);
1756 if (aNewWorkerCreated) {
1757 *aNewWorkerCreated = true;
1760 return NS_OK;
1763 bool ServiceWorkerPrivate::MaybeStoreISupports(nsISupports* aSupports) {
1764 MOZ_ASSERT(NS_IsMainThread());
1766 if (!mWorkerPrivate) {
1767 MOZ_DIAGNOSTIC_ASSERT(mSupportsArray.IsEmpty());
1768 return false;
1771 MOZ_ASSERT(!mSupportsArray.Contains(aSupports));
1772 mSupportsArray.AppendElement(aSupports);
1773 return true;
1776 void ServiceWorkerPrivate::RemoveISupports(nsISupports* aSupports) {
1777 MOZ_ASSERT(NS_IsMainThread());
1778 mSupportsArray.RemoveElement(aSupports);
1781 void ServiceWorkerPrivate::TerminateWorker() {
1782 MOZ_ASSERT(NS_IsMainThread());
1784 mIdleWorkerTimer->Cancel();
1785 mIdleKeepAliveToken = nullptr;
1786 if (mWorkerPrivate) {
1787 if (StaticPrefs::dom_serviceWorkers_testing_enabled()) {
1788 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1789 if (os) {
1790 os->NotifyObservers(nullptr, "service-worker-shutdown", nullptr);
1794 Unused << NS_WARN_IF(!mWorkerPrivate->Cancel());
1795 RefPtr<WorkerPrivate> workerPrivate(mWorkerPrivate.forget());
1796 mSupportsArray.Clear();
1798 // Any pending events are never going to fire on this worker. Cancel
1799 // them so that intercepted channels can be reset and other resources
1800 // cleaned up.
1801 nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
1802 mPendingFunctionalEvents.SwapElements(pendingEvents);
1803 for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1804 pendingEvents[i]->Cancel();
1809 void ServiceWorkerPrivate::NoteDeadServiceWorkerInfo() {
1810 MOZ_ASSERT(NS_IsMainThread());
1811 mInfo = nullptr;
1812 TerminateWorker();
1815 namespace {
1817 class UpdateStateControlRunnable final
1818 : public MainThreadWorkerControlRunnable {
1819 const ServiceWorkerState mState;
1821 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1822 MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
1823 aWorkerPrivate->UpdateServiceWorkerState(mState);
1824 return true;
1827 public:
1828 UpdateStateControlRunnable(WorkerPrivate* aWorkerPrivate,
1829 ServiceWorkerState aState)
1830 : MainThreadWorkerControlRunnable(aWorkerPrivate), mState(aState) {}
1833 } // anonymous namespace
1835 void ServiceWorkerPrivate::UpdateState(ServiceWorkerState aState) {
1836 MOZ_ASSERT(NS_IsMainThread());
1838 if (!mWorkerPrivate) {
1839 MOZ_DIAGNOSTIC_ASSERT(mPendingFunctionalEvents.IsEmpty());
1840 return;
1843 RefPtr<WorkerRunnable> r =
1844 new UpdateStateControlRunnable(mWorkerPrivate, aState);
1845 Unused << r->Dispatch();
1847 if (aState != ServiceWorkerState::Activated) {
1848 return;
1851 nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
1852 mPendingFunctionalEvents.SwapElements(pendingEvents);
1854 for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
1855 RefPtr<WorkerRunnable> r = pendingEvents[i].forget();
1856 if (NS_WARN_IF(!r->Dispatch())) {
1857 NS_WARNING("Failed to dispatch pending functional event!");
1862 nsresult ServiceWorkerPrivate::GetDebugger(nsIWorkerDebugger** aResult) {
1863 MOZ_ASSERT(NS_IsMainThread());
1864 MOZ_ASSERT(aResult);
1866 if (!mDebuggerCount) {
1867 return NS_OK;
1870 MOZ_ASSERT(mWorkerPrivate);
1872 nsCOMPtr<nsIWorkerDebugger> debugger = mWorkerPrivate->Debugger();
1873 debugger.forget(aResult);
1875 return NS_OK;
1878 nsresult ServiceWorkerPrivate::AttachDebugger() {
1879 MOZ_ASSERT(NS_IsMainThread());
1881 // When the first debugger attaches to a worker, we spawn a worker if needed,
1882 // and cancel the idle timeout. The idle timeout should not be reset until
1883 // the last debugger detached from the worker.
1884 if (!mDebuggerCount) {
1885 nsresult rv = SpawnWorkerIfNeeded(AttachEvent);
1886 NS_ENSURE_SUCCESS(rv, rv);
1888 mIdleWorkerTimer->Cancel();
1891 ++mDebuggerCount;
1893 return NS_OK;
1896 nsresult ServiceWorkerPrivate::DetachDebugger() {
1897 MOZ_ASSERT(NS_IsMainThread());
1899 if (!mDebuggerCount) {
1900 return NS_ERROR_UNEXPECTED;
1903 --mDebuggerCount;
1905 // When the last debugger detaches from a worker, we either reset the idle
1906 // timeout, or terminate the worker if there are no more active tokens.
1907 if (!mDebuggerCount) {
1908 if (mTokenCount) {
1909 ResetIdleTimeout();
1910 } else {
1911 TerminateWorker();
1915 return NS_OK;
1918 bool ServiceWorkerPrivate::IsIdle() const {
1919 MOZ_ASSERT(NS_IsMainThread());
1920 return mTokenCount == 0 || (mTokenCount == 1 && mIdleKeepAliveToken);
1923 namespace {
1925 class ServiceWorkerPrivateTimerCallback final : public nsITimerCallback,
1926 public nsINamed {
1927 public:
1928 typedef void (ServiceWorkerPrivate::*Method)(nsITimer*);
1930 ServiceWorkerPrivateTimerCallback(ServiceWorkerPrivate* aServiceWorkerPrivate,
1931 Method aMethod)
1932 : mServiceWorkerPrivate(aServiceWorkerPrivate), mMethod(aMethod) {}
1934 NS_IMETHOD
1935 Notify(nsITimer* aTimer) override {
1936 (mServiceWorkerPrivate->*mMethod)(aTimer);
1937 mServiceWorkerPrivate = nullptr;
1938 return NS_OK;
1941 NS_IMETHOD
1942 GetName(nsACString& aName) override {
1943 aName.AssignLiteral("ServiceWorkerPrivateTimerCallback");
1944 return NS_OK;
1947 private:
1948 ~ServiceWorkerPrivateTimerCallback() = default;
1950 RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
1951 Method mMethod;
1953 NS_DECL_THREADSAFE_ISUPPORTS
1956 NS_IMPL_ISUPPORTS(ServiceWorkerPrivateTimerCallback, nsITimerCallback,
1957 nsINamed);
1959 } // anonymous namespace
1961 void ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer) {
1962 MOZ_ASSERT(NS_IsMainThread());
1964 MOZ_ASSERT(aTimer == mIdleWorkerTimer, "Invalid timer!");
1966 // Release ServiceWorkerPrivate's token, since the grace period has ended.
1967 mIdleKeepAliveToken = nullptr;
1969 if (mWorkerPrivate) {
1970 // If we still have a workerPrivate at this point it means there are pending
1971 // waitUntil promises. Wait a bit more until we forcibly terminate the
1972 // worker.
1973 uint32_t timeout =
1974 Preferences::GetInt("dom.serviceWorkers.idle_extended_timeout");
1975 nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
1976 this, &ServiceWorkerPrivate::TerminateWorkerCallback);
1977 DebugOnly<nsresult> rv = mIdleWorkerTimer->InitWithCallback(
1978 cb, timeout, nsITimer::TYPE_ONE_SHOT);
1979 MOZ_ASSERT(NS_SUCCEEDED(rv));
1983 void ServiceWorkerPrivate::TerminateWorkerCallback(nsITimer* aTimer) {
1984 MOZ_ASSERT(NS_IsMainThread());
1986 MOZ_ASSERT(aTimer == this->mIdleWorkerTimer, "Invalid timer!");
1988 // mInfo must be non-null at this point because NoteDeadServiceWorkerInfo
1989 // which zeroes it calls TerminateWorker which cancels our timer which will
1990 // ensure we don't get invoked even if the nsTimerEvent is in the event queue.
1991 ServiceWorkerManager::LocalizeAndReportToAllClients(
1992 mInfo->Scope(), "ServiceWorkerGraceTimeoutTermination",
1993 nsTArray<nsString>{NS_ConvertUTF8toUTF16(mInfo->Scope())});
1995 TerminateWorker();
1998 void ServiceWorkerPrivate::RenewKeepAliveToken(WakeUpReason aWhy) {
1999 // We should have an active worker if we're renewing the keep alive token.
2000 MOZ_ASSERT(mWorkerPrivate);
2002 // If there is at least one debugger attached to the worker, the idle worker
2003 // timeout was canceled when the first debugger attached to the worker. It
2004 // should not be reset until the last debugger detaches from the worker.
2005 if (!mDebuggerCount) {
2006 ResetIdleTimeout();
2009 if (!mIdleKeepAliveToken) {
2010 mIdleKeepAliveToken = new KeepAliveToken(this);
2014 void ServiceWorkerPrivate::ResetIdleTimeout() {
2015 uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout");
2016 nsCOMPtr<nsITimerCallback> cb = new ServiceWorkerPrivateTimerCallback(
2017 this, &ServiceWorkerPrivate::NoteIdleWorkerCallback);
2018 DebugOnly<nsresult> rv =
2019 mIdleWorkerTimer->InitWithCallback(cb, timeout, nsITimer::TYPE_ONE_SHOT);
2020 MOZ_ASSERT(NS_SUCCEEDED(rv));
2023 void ServiceWorkerPrivate::AddToken() {
2024 MOZ_ASSERT(NS_IsMainThread());
2025 ++mTokenCount;
2028 void ServiceWorkerPrivate::ReleaseToken() {
2029 MOZ_ASSERT(NS_IsMainThread());
2031 MOZ_ASSERT(mTokenCount > 0);
2032 --mTokenCount;
2033 if (!mTokenCount) {
2034 TerminateWorker();
2037 // mInfo can be nullptr here if NoteDeadServiceWorkerInfo() is called while
2038 // the KeepAliveToken is being proxy released as a runnable.
2039 else if (mInfo && IsIdle()) {
2040 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
2041 if (swm) {
2042 swm->WorkerIsIdle(mInfo);
2047 already_AddRefed<KeepAliveToken>
2048 ServiceWorkerPrivate::CreateEventKeepAliveToken() {
2049 MOZ_ASSERT(NS_IsMainThread());
2050 MOZ_ASSERT(mWorkerPrivate);
2051 MOZ_ASSERT(mIdleKeepAliveToken);
2052 RefPtr<KeepAliveToken> ref = new KeepAliveToken(this);
2053 return ref.forget();
2056 void ServiceWorkerPrivate::SetHandlesFetch(bool aValue) {
2057 MOZ_ASSERT(NS_IsMainThread());
2059 if (NS_WARN_IF(!mInfo)) {
2060 return;
2063 mInfo->SetHandlesFetch(aValue);
2066 } // namespace dom
2067 } // namespace mozilla