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