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