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 "ServiceWorkerContainer.h"
8 
9 #include "nsContentPolicyUtils.h"
10 #include "nsContentSecurityManager.h"
11 #include "nsContentUtils.h"
12 #include "mozilla/dom/Document.h"
13 #include "nsIServiceWorkerManager.h"
14 #include "nsIScriptError.h"
15 #include "nsThreadUtils.h"
16 #include "nsNetUtil.h"
17 #include "nsPIDOMWindow.h"
18 #include "mozilla/Components.h"
19 #include "mozilla/StaticPrefs_dom.h"
20 
21 #include "nsCycleCollectionParticipant.h"
22 #include "nsServiceManagerUtils.h"
23 #include "mozilla/BasePrincipal.h"
24 #include "mozilla/LoadInfo.h"
25 #include "mozilla/SchedulerGroup.h"
26 #include "mozilla/StaticPrefs_extensions.h"
27 #include "mozilla/StorageAccess.h"
28 #include "mozilla/dom/ClientIPCTypes.h"
29 #include "mozilla/dom/DOMMozPromiseRequestHolder.h"
30 #include "mozilla/dom/MessageEvent.h"
31 #include "mozilla/dom/MessageEventBinding.h"
32 #include "mozilla/dom/Navigator.h"
33 #include "mozilla/dom/Promise.h"
34 #include "mozilla/dom/ServiceWorker.h"
35 #include "mozilla/dom/ServiceWorkerContainerBinding.h"
36 #include "mozilla/dom/ServiceWorkerManager.h"
37 #include "mozilla/dom/ipc/StructuredCloneData.h"
38 
39 #include "RemoteServiceWorkerContainerImpl.h"
40 #include "ServiceWorker.h"
41 #include "ServiceWorkerRegistration.h"
42 #include "ServiceWorkerUtils.h"
43 
44 // This is defined to something else on Windows
45 #ifdef DispatchMessage
46 #  undef DispatchMessage
47 #endif
48 
49 namespace mozilla {
50 namespace dom {
51 
52 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerContainer)
53 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
54 
55 NS_IMPL_ADDREF_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
56 NS_IMPL_RELEASE_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper)
57 
58 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper,
59                                    mControllerWorker, mReadyPromise)
60 
61 namespace {
62 
IsInPrivateBrowsing(JSContext * const aCx)63 bool IsInPrivateBrowsing(JSContext* const aCx) {
64   if (const nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx)) {
65     if (const nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull()) {
66       return principal->GetPrivateBrowsingId() > 0;
67     }
68   }
69   return false;
70 }
71 
IsServiceWorkersTestingEnabledInWindow(JSObject * const aGlobal)72 bool IsServiceWorkersTestingEnabledInWindow(JSObject* const aGlobal) {
73   if (const nsCOMPtr<nsPIDOMWindowInner> innerWindow =
74           Navigator::GetWindowFromGlobal(aGlobal)) {
75     if (auto* bc = innerWindow->GetBrowsingContext()) {
76       return bc->Top()->ServiceWorkersTestingEnabled();
77     }
78   }
79   return false;
80 }
81 
82 }  // namespace
83 
84 /* static */
IsEnabled(JSContext * aCx,JSObject * aGlobal)85 bool ServiceWorkerContainer::IsEnabled(JSContext* aCx, JSObject* aGlobal) {
86   MOZ_ASSERT(NS_IsMainThread());
87 
88   // FIXME: Why does this need to root? Shouldn't the caller root aGlobal for
89   // us?
90   JS::Rooted<JSObject*> global(aCx, aGlobal);
91 
92   if (!StaticPrefs::dom_serviceWorkers_enabled()) {
93     return false;
94   }
95 
96   if (IsInPrivateBrowsing(aCx)) {
97     return false;
98   }
99 
100   if (IsSecureContextOrObjectIsFromSecureContext(aCx, global)) {
101     return true;
102   }
103 
104   return StaticPrefs::dom_serviceWorkers_testing_enabled() ||
105          IsServiceWorkersTestingEnabledInWindow(global);
106 }
107 
108 // static
Create(nsIGlobalObject * aGlobal)109 already_AddRefed<ServiceWorkerContainer> ServiceWorkerContainer::Create(
110     nsIGlobalObject* aGlobal) {
111   RefPtr<Inner> inner = new RemoteServiceWorkerContainerImpl();
112   RefPtr<ServiceWorkerContainer> ref =
113       new ServiceWorkerContainer(aGlobal, inner.forget());
114   return ref.forget();
115 }
116 
ServiceWorkerContainer(nsIGlobalObject * aGlobal,already_AddRefed<ServiceWorkerContainer::Inner> aInner)117 ServiceWorkerContainer::ServiceWorkerContainer(
118     nsIGlobalObject* aGlobal,
119     already_AddRefed<ServiceWorkerContainer::Inner> aInner)
120     : DOMEventTargetHelper(aGlobal), mInner(aInner) {
121   mInner->AddContainer(this);
122   Maybe<ServiceWorkerDescriptor> controller = aGlobal->GetController();
123   if (controller.isSome()) {
124     mControllerWorker = aGlobal->GetOrCreateServiceWorker(controller.ref());
125   }
126 }
127 
~ServiceWorkerContainer()128 ServiceWorkerContainer::~ServiceWorkerContainer() {
129   mInner->RemoveContainer(this);
130 }
131 
DisconnectFromOwner()132 void ServiceWorkerContainer::DisconnectFromOwner() {
133   mControllerWorker = nullptr;
134   mReadyPromise = nullptr;
135   DOMEventTargetHelper::DisconnectFromOwner();
136 }
137 
ControllerChanged(ErrorResult & aRv)138 void ServiceWorkerContainer::ControllerChanged(ErrorResult& aRv) {
139   nsCOMPtr<nsIGlobalObject> go = GetParentObject();
140   if (!go) {
141     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
142     return;
143   }
144   mControllerWorker = go->GetOrCreateServiceWorker(go->GetController().ref());
145   aRv = DispatchTrustedEvent(u"controllerchange"_ns);
146 }
147 
148 using mozilla::dom::ipc::StructuredCloneData;
149 
150 // A ReceivedMessage represents a message sent via
151 // Client.postMessage(). It is used as used both for queuing of
152 // incoming messages and as an interface to DispatchMessage().
153 struct MOZ_HEAP_CLASS ServiceWorkerContainer::ReceivedMessage {
ReceivedMessagemozilla::dom::ServiceWorkerContainer::ReceivedMessage154   explicit ReceivedMessage(const ClientPostMessageArgs& aArgs)
155       : mServiceWorker(aArgs.serviceWorker()) {
156     mClonedData.CopyFromClonedMessageDataForBackgroundChild(aArgs.clonedData());
157   }
158 
159   ServiceWorkerDescriptor mServiceWorker;
160   StructuredCloneData mClonedData;
161 
162   NS_INLINE_DECL_REFCOUNTING(ReceivedMessage)
163 
164  private:
165   ~ReceivedMessage() = default;
166 };
167 
ReceiveMessage(const ClientPostMessageArgs & aArgs)168 void ServiceWorkerContainer::ReceiveMessage(
169     const ClientPostMessageArgs& aArgs) {
170   RefPtr<ReceivedMessage> message = new ReceivedMessage(aArgs);
171   if (mMessagesStarted) {
172     EnqueueReceivedMessageDispatch(std::move(message));
173   } else {
174     mPendingMessages.AppendElement(message.forget());
175   }
176 }
177 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)178 JSObject* ServiceWorkerContainer::WrapObject(
179     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
180   return ServiceWorkerContainer_Binding::Wrap(aCx, this, aGivenProto);
181 }
182 
183 namespace {
184 
GetBaseURIFromGlobal(nsIGlobalObject * aGlobal,ErrorResult & aRv)185 already_AddRefed<nsIURI> GetBaseURIFromGlobal(nsIGlobalObject* aGlobal,
186                                               ErrorResult& aRv) {
187   // It would be nice not to require a window here, but right
188   // now we don't have a great way to get the base URL just
189   // from the nsIGlobalObject.
190   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
191   if (!window) {
192     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
193     return nullptr;
194   }
195 
196   Document* doc = window->GetExtantDoc();
197   if (!doc) {
198     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
199     return nullptr;
200   }
201 
202   nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI();
203   if (!baseURI) {
204     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
205     return nullptr;
206   }
207 
208   return baseURI.forget();
209 }
210 
211 }  // anonymous namespace
212 
Register(const nsAString & aScriptURL,const RegistrationOptions & aOptions,const CallerType aCallerType,ErrorResult & aRv)213 already_AddRefed<Promise> ServiceWorkerContainer::Register(
214     const nsAString& aScriptURL, const RegistrationOptions& aOptions,
215     const CallerType aCallerType, ErrorResult& aRv) {
216   // Note, we can't use GetGlobalIfValid() from the start here.  If we
217   // hit a storage failure we want to log a message with the final
218   // scope string we put together below.
219   nsIGlobalObject* global = GetParentObject();
220   if (!global) {
221     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
222     return nullptr;
223   }
224 
225   Maybe<ClientInfo> clientInfo = global->GetClientInfo();
226   if (clientInfo.isNothing()) {
227     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
228     return nullptr;
229   }
230 
231   nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
232   if (aRv.Failed()) {
233     return nullptr;
234   }
235 
236   // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
237   nsAutoCString scriptURL;
238   if (!AppendUTF16toUTF8(aScriptURL, scriptURL, fallible)) {
239     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
240     return nullptr;
241   }
242 
243   nsCOMPtr<nsIURI> scriptURI;
244   nsresult rv =
245       NS_NewURI(getter_AddRefs(scriptURI), scriptURL, nullptr, baseURI);
246   if (NS_WARN_IF(NS_FAILED(rv))) {
247     aRv.ThrowTypeError<MSG_INVALID_URL>(scriptURL);
248     return nullptr;
249   }
250 
251   // Never allow script URL with moz-extension scheme if support is fully
252   // disabled by the 'extensions.background_service_worker.enabled' pref.
253   if (scriptURI->SchemeIs("moz-extension") &&
254       !StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
255     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
256     return nullptr;
257   }
258 
259   // Allow a webextension principal to register a service worker script with
260   // a moz-extension url only if 'extensions.service_worker_register.allowed'
261   // is true.
262   if (scriptURI->SchemeIs("moz-extension") &&
263       aCallerType == CallerType::NonSystem &&
264       (!baseURI->SchemeIs("moz-extension") ||
265        !StaticPrefs::extensions_serviceWorkerRegister_allowed())) {
266     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
267     return nullptr;
268   }
269 
270   // In ServiceWorkerContainer.register() the scope argument is parsed against
271   // different base URLs depending on whether it was passed or not.
272   nsCOMPtr<nsIURI> scopeURI;
273 
274   // Step 4. If none passed, parse against script's URL
275   if (!aOptions.mScope.WasPassed()) {
276     constexpr auto defaultScope = "./"_ns;
277     rv = NS_NewURI(getter_AddRefs(scopeURI), defaultScope, nullptr, scriptURI);
278     if (NS_WARN_IF(NS_FAILED(rv))) {
279       nsAutoCString spec;
280       scriptURI->GetSpec(spec);
281       aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, spec);
282       return nullptr;
283     }
284   } else {
285     // Step 5. Parse against entry settings object's base URL.
286     rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(), nullptr,
287                    baseURI);
288     if (NS_WARN_IF(NS_FAILED(rv))) {
289       nsIURI* uri = baseURI ? baseURI : scriptURI;
290       nsAutoCString spec;
291       uri->GetSpec(spec);
292       aRv.ThrowTypeError<MSG_INVALID_SCOPE>(
293           NS_ConvertUTF16toUTF8(aOptions.mScope.Value()), spec);
294       return nullptr;
295     }
296   }
297 
298   // Strip the any ref from both the script and scope URLs.
299   nsCOMPtr<nsIURI> cloneWithoutRef;
300   aRv = NS_GetURIWithoutRef(scriptURI, getter_AddRefs(cloneWithoutRef));
301   if (aRv.Failed()) {
302     return nullptr;
303   }
304   scriptURI = std::move(cloneWithoutRef);
305 
306   aRv = NS_GetURIWithoutRef(scopeURI, getter_AddRefs(cloneWithoutRef));
307   if (aRv.Failed()) {
308     return nullptr;
309   }
310   scopeURI = std::move(cloneWithoutRef);
311 
312   ServiceWorkerScopeAndScriptAreValid(clientInfo.ref(), scopeURI, scriptURI,
313                                       aRv);
314   if (aRv.Failed()) {
315     return nullptr;
316   }
317 
318   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
319   if (!window) {
320     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
321     return nullptr;
322   }
323 
324   Document* doc = window->GetExtantDoc();
325   if (!doc) {
326     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
327     return nullptr;
328   }
329 
330   // The next section of code executes an NS_CheckContentLoadPolicy()
331   // check.  This is necessary to enforce the CSP of the calling client.
332   // Currently this requires an Document.  Once bug 965637 lands we
333   // should try to move this into ServiceWorkerScopeAndScriptAreValid()
334   // using the ClientInfo instead of doing a window-specific check here.
335   // See bug 1455077 for further investigation.
336   nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new mozilla::net::LoadInfo(
337       doc->NodePrincipal(),  // loading principal
338       doc->NodePrincipal(),  // triggering principal
339       doc,                   // loading node
340       nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
341       nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER);
342 
343   // Check content policy.
344   int16_t decision = nsIContentPolicy::ACCEPT;
345   rv = NS_CheckContentLoadPolicy(scriptURI, secCheckLoadInfo,
346                                  "application/javascript"_ns, &decision);
347   if (NS_FAILED(rv)) {
348     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
349     return nullptr;
350   }
351   if (NS_WARN_IF(decision != nsIContentPolicy::ACCEPT)) {
352     aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
353     return nullptr;
354   }
355 
356   // Get the string representation for both the script and scope since
357   // we sanitized them above.
358   nsCString cleanedScopeURL;
359   aRv = scopeURI->GetSpec(cleanedScopeURL);
360   if (aRv.Failed()) {
361     return nullptr;
362   }
363 
364   nsCString cleanedScriptURL;
365   aRv = scriptURI->GetSpec(cleanedScriptURL);
366   if (aRv.Failed()) {
367     return nullptr;
368   }
369 
370   // Verify that the global is valid and has permission to store
371   // data.  We perform this late so that we can report the final
372   // scope URL in any error message.
373   Unused << GetGlobalIfValid(aRv, [&](Document* aDoc) {
374     AutoTArray<nsString, 1> param;
375     CopyUTF8toUTF16(cleanedScopeURL, *param.AppendElement());
376     nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
377                                     "Service Workers"_ns, aDoc,
378                                     nsContentUtils::eDOM_PROPERTIES,
379                                     "ServiceWorkerRegisterStorageError", param);
380   });
381 
382   window->NoteCalledRegisterForServiceWorkerScope(cleanedScopeURL);
383 
384   RefPtr<Promise> outer =
385       Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
386   if (aRv.Failed()) {
387     return nullptr;
388   }
389 
390   RefPtr<ServiceWorkerContainer> self = this;
391 
392   mInner->Register(
393       clientInfo.ref(), cleanedScopeURL, cleanedScriptURL,
394       aOptions.mUpdateViaCache,
395       [self, outer](const ServiceWorkerRegistrationDescriptor& aDesc) {
396         ErrorResult rv;
397         nsIGlobalObject* global = self->GetGlobalIfValid(rv);
398         if (rv.Failed()) {
399           outer->MaybeReject(std::move(rv));
400           return;
401         }
402         RefPtr<ServiceWorkerRegistration> reg =
403             global->GetOrCreateServiceWorkerRegistration(aDesc);
404         outer->MaybeResolve(reg);
405       },
406       [outer](ErrorResult&& aRv) { outer->MaybeReject(std::move(aRv)); });
407 
408   return outer.forget();
409 }
410 
GetController()411 already_AddRefed<ServiceWorker> ServiceWorkerContainer::GetController() {
412   RefPtr<ServiceWorker> ref = mControllerWorker;
413   return ref.forget();
414 }
415 
GetRegistrations(ErrorResult & aRv)416 already_AddRefed<Promise> ServiceWorkerContainer::GetRegistrations(
417     ErrorResult& aRv) {
418   nsIGlobalObject* global = GetGlobalIfValid(aRv, [](Document* aDoc) {
419     nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
420                                     "Service Workers"_ns, aDoc,
421                                     nsContentUtils::eDOM_PROPERTIES,
422                                     "ServiceWorkerGetRegistrationStorageError");
423   });
424   if (aRv.Failed()) {
425     return nullptr;
426   }
427 
428   Maybe<ClientInfo> clientInfo = global->GetClientInfo();
429   if (clientInfo.isNothing()) {
430     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
431     return nullptr;
432   }
433 
434   RefPtr<Promise> outer =
435       Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
436   if (aRv.Failed()) {
437     return nullptr;
438   }
439 
440   RefPtr<ServiceWorkerContainer> self = this;
441 
442   mInner->GetRegistrations(
443       clientInfo.ref(),
444       [self,
445        outer](const nsTArray<ServiceWorkerRegistrationDescriptor>& aDescList) {
446         ErrorResult rv;
447         nsIGlobalObject* global = self->GetGlobalIfValid(rv);
448         if (rv.Failed()) {
449           outer->MaybeReject(std::move(rv));
450           return;
451         }
452         nsTArray<RefPtr<ServiceWorkerRegistration>> regList;
453         for (auto& desc : aDescList) {
454           RefPtr<ServiceWorkerRegistration> reg =
455               global->GetOrCreateServiceWorkerRegistration(desc);
456           if (reg) {
457             regList.AppendElement(std::move(reg));
458           }
459         }
460         outer->MaybeResolve(regList);
461       },
462       [self, outer](ErrorResult&& aRv) { outer->MaybeReject(std::move(aRv)); });
463 
464   return outer.forget();
465 }
466 
StartMessages()467 void ServiceWorkerContainer::StartMessages() {
468   while (!mPendingMessages.IsEmpty()) {
469     EnqueueReceivedMessageDispatch(mPendingMessages.ElementAt(0));
470     mPendingMessages.RemoveElementAt(0);
471   }
472   mMessagesStarted = true;
473 }
474 
GetRegistration(const nsAString & aURL,ErrorResult & aRv)475 already_AddRefed<Promise> ServiceWorkerContainer::GetRegistration(
476     const nsAString& aURL, ErrorResult& aRv) {
477   nsIGlobalObject* global = GetGlobalIfValid(aRv, [](Document* aDoc) {
478     nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
479                                     "Service Workers"_ns, aDoc,
480                                     nsContentUtils::eDOM_PROPERTIES,
481                                     "ServiceWorkerGetRegistrationStorageError");
482   });
483   if (aRv.Failed()) {
484     return nullptr;
485   }
486 
487   Maybe<ClientInfo> clientInfo = global->GetClientInfo();
488   if (clientInfo.isNothing()) {
489     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
490     return nullptr;
491   }
492 
493   nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
494   if (aRv.Failed()) {
495     return nullptr;
496   }
497 
498   nsCOMPtr<nsIURI> uri;
499   aRv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, baseURI);
500   if (aRv.Failed()) {
501     return nullptr;
502   }
503 
504   nsCString spec;
505   aRv = uri->GetSpec(spec);
506   if (aRv.Failed()) {
507     return nullptr;
508   }
509 
510   RefPtr<Promise> outer =
511       Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
512   if (aRv.Failed()) {
513     return nullptr;
514   }
515 
516   RefPtr<ServiceWorkerContainer> self = this;
517 
518   mInner->GetRegistration(
519       clientInfo.ref(), spec,
520       [self, outer](const ServiceWorkerRegistrationDescriptor& aDescriptor) {
521         ErrorResult rv;
522         nsIGlobalObject* global = self->GetGlobalIfValid(rv);
523         if (rv.Failed()) {
524           outer->MaybeReject(std::move(rv));
525           return;
526         }
527         RefPtr<ServiceWorkerRegistration> reg =
528             global->GetOrCreateServiceWorkerRegistration(aDescriptor);
529         outer->MaybeResolve(reg);
530       },
531       [self, outer](ErrorResult&& aRv) {
532         if (!aRv.Failed()) {
533           Unused << self->GetGlobalIfValid(aRv);
534           if (!aRv.Failed()) {
535             outer->MaybeResolveWithUndefined();
536             return;
537           }
538         }
539         outer->MaybeReject(std::move(aRv));
540       });
541 
542   return outer.forget();
543 }
544 
GetReady(ErrorResult & aRv)545 Promise* ServiceWorkerContainer::GetReady(ErrorResult& aRv) {
546   if (mReadyPromise) {
547     return mReadyPromise;
548   }
549 
550   nsIGlobalObject* global = GetGlobalIfValid(aRv);
551   if (aRv.Failed()) {
552     return nullptr;
553   }
554   MOZ_DIAGNOSTIC_ASSERT(global);
555 
556   Maybe<ClientInfo> clientInfo(global->GetClientInfo());
557   if (clientInfo.isNothing()) {
558     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
559     return nullptr;
560   }
561 
562   mReadyPromise =
563       Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
564   if (aRv.Failed()) {
565     return nullptr;
566   }
567 
568   RefPtr<ServiceWorkerContainer> self = this;
569   RefPtr<Promise> outer = mReadyPromise;
570 
571   mInner->GetReady(
572       clientInfo.ref(),
573       [self, outer](const ServiceWorkerRegistrationDescriptor& aDescriptor) {
574         ErrorResult rv;
575         nsIGlobalObject* global = self->GetGlobalIfValid(rv);
576         if (rv.Failed()) {
577           outer->MaybeReject(std::move(rv));
578           return;
579         }
580         RefPtr<ServiceWorkerRegistration> reg =
581             global->GetOrCreateServiceWorkerRegistration(aDescriptor);
582         NS_ENSURE_TRUE_VOID(reg);
583 
584         // Don't resolve the ready promise until the registration has
585         // reached the right version.  This ensures that the active
586         // worker property is set correctly on the registration.
587         reg->WhenVersionReached(
588             aDescriptor.Version(),
589             [outer, reg](bool aResult) { outer->MaybeResolve(reg); });
590       },
591       [self, outer](ErrorResult&& aRv) { outer->MaybeReject(std::move(aRv)); });
592 
593   return mReadyPromise;
594 }
595 
596 // Testing only.
GetScopeForUrl(const nsAString & aUrl,nsString & aScope,ErrorResult & aRv)597 void ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
598                                             nsString& aScope,
599                                             ErrorResult& aRv) {
600   nsCOMPtr<nsIServiceWorkerManager> swm =
601       mozilla::components::ServiceWorkerManager::Service();
602   if (!swm) {
603     aRv.Throw(NS_ERROR_FAILURE);
604     return;
605   }
606 
607   nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
608   if (NS_WARN_IF(!window)) {
609     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
610     return;
611   }
612 
613   nsCOMPtr<Document> doc = window->GetExtantDoc();
614   if (NS_WARN_IF(!doc)) {
615     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
616     return;
617   }
618 
619   aRv = swm->GetScopeForUrl(doc->NodePrincipal(), aUrl, aScope);
620 }
621 
GetGlobalIfValid(ErrorResult & aRv,const std::function<void (Document *)> && aStorageFailureCB) const622 nsIGlobalObject* ServiceWorkerContainer::GetGlobalIfValid(
623     ErrorResult& aRv,
624     const std::function<void(Document*)>&& aStorageFailureCB) const {
625   // For now we require a window since ServiceWorkerContainer is
626   // not exposed on worker globals yet.  The main thing we need
627   // to fix here to support that is the storage access check via
628   // the nsIGlobalObject.
629   nsPIDOMWindowInner* window = GetOwner();
630   if (NS_WARN_IF(!window)) {
631     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
632     return nullptr;
633   }
634 
635   nsCOMPtr<Document> doc = window->GetExtantDoc();
636   if (NS_WARN_IF(!doc)) {
637     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
638     return nullptr;
639   }
640 
641   // Don't allow a service worker to access service worker registrations
642   // from a window with storage disabled.  If these windows can access
643   // the registration it increases the chance they can bypass the storage
644   // block via postMessage(), etc.
645   auto storageAllowed = StorageAllowedForWindow(window);
646   if (NS_WARN_IF(storageAllowed != StorageAccess::eAllow)) {
647     if (aStorageFailureCB) {
648       aStorageFailureCB(doc);
649     }
650     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
651     return nullptr;
652   }
653 
654   // Don't allow service workers when the document is chrome.
655   if (NS_WARN_IF(doc->NodePrincipal()->IsSystemPrincipal())) {
656     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
657     return nullptr;
658   }
659 
660   return window->AsGlobal();
661 }
662 
EnqueueReceivedMessageDispatch(RefPtr<ReceivedMessage> aMessage)663 void ServiceWorkerContainer::EnqueueReceivedMessageDispatch(
664     RefPtr<ReceivedMessage> aMessage) {
665   if (nsPIDOMWindowInner* const window = GetOwner()) {
666     if (auto* const target = window->EventTargetFor(TaskCategory::Other)) {
667       target->Dispatch(NewRunnableMethod<RefPtr<ReceivedMessage>>(
668           "ServiceWorkerContainer::DispatchMessage", this,
669           &ServiceWorkerContainer::DispatchMessage, std::move(aMessage)));
670     }
671   }
672 }
673 
674 template <typename F>
RunWithJSContext(F && aCallable)675 void ServiceWorkerContainer::RunWithJSContext(F&& aCallable) {
676   nsCOMPtr<nsIGlobalObject> globalObject;
677   if (nsPIDOMWindowInner* const window = GetOwner()) {
678     globalObject = do_QueryInterface(window);
679   }
680 
681   // If AutoJSAPI::Init() fails then either global is nullptr or not
682   // in a usable state.
683   AutoJSAPI jsapi;
684   if (!jsapi.Init(globalObject)) {
685     return;
686   }
687 
688   aCallable(jsapi.cx(), globalObject);
689 }
690 
DispatchMessage(RefPtr<ReceivedMessage> aMessage)691 void ServiceWorkerContainer::DispatchMessage(RefPtr<ReceivedMessage> aMessage) {
692   MOZ_ASSERT(NS_IsMainThread());
693 
694   // When dispatching a message, either DOMContentLoaded has already
695   // been fired, or someone called startMessages() or set onmessage.
696   // Either way, a global object is supposed to be present. If it's
697   // not, we'd fail to initialize the JS API and exit.
698   RunWithJSContext([this, message = std::move(aMessage)](
699                        JSContext* const aCx, nsIGlobalObject* const aGlobal) {
700     ErrorResult result;
701     bool deserializationFailed = false;
702     RootedDictionary<MessageEventInit> init(aCx);
703     auto res = FillInMessageEventInit(aCx, aGlobal, *message, init, result);
704     if (res.isErr()) {
705       deserializationFailed = res.unwrapErr();
706       MOZ_ASSERT_IF(deserializationFailed, init.mData.isNull());
707       MOZ_ASSERT_IF(deserializationFailed, init.mPorts.IsEmpty());
708       MOZ_ASSERT_IF(deserializationFailed, !init.mOrigin.IsEmpty());
709       MOZ_ASSERT_IF(deserializationFailed, !init.mSource.IsNull());
710       result.SuppressException();
711 
712       if (!deserializationFailed && result.MaybeSetPendingException(aCx)) {
713         return;
714       }
715     }
716 
717     RefPtr<MessageEvent> event = MessageEvent::Constructor(
718         this, deserializationFailed ? u"messageerror"_ns : u"message"_ns, init);
719     event->SetTrusted(true);
720 
721     result = NS_OK;
722     DispatchEvent(*event, result);
723     if (result.Failed()) {
724       result.SuppressException();
725     }
726   });
727 }
728 
729 namespace {
730 
FillInOriginNoSuffix(const ServiceWorkerDescriptor & aServiceWorker,nsString & aOrigin)731 nsresult FillInOriginNoSuffix(const ServiceWorkerDescriptor& aServiceWorker,
732                               nsString& aOrigin) {
733   using mozilla::ipc::PrincipalInfoToPrincipal;
734 
735   nsresult rv;
736 
737   auto principalOrErr =
738       PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());
739   if (NS_WARN_IF(principalOrErr.isErr())) {
740     return principalOrErr.unwrapErr();
741   }
742 
743   nsAutoCString originUTF8;
744   rv = principalOrErr.unwrap()->GetOriginNoSuffix(originUTF8);
745   if (NS_FAILED(rv)) {
746     return rv;
747   }
748 
749   CopyUTF8toUTF16(originUTF8, aOrigin);
750   return NS_OK;
751 }
752 
753 }  // namespace
754 
FillInMessageEventInit(JSContext * const aCx,nsIGlobalObject * const aGlobal,ReceivedMessage & aMessage,MessageEventInit & aInit,ErrorResult & aRv)755 Result<Ok, bool> ServiceWorkerContainer::FillInMessageEventInit(
756     JSContext* const aCx, nsIGlobalObject* const aGlobal,
757     ReceivedMessage& aMessage, MessageEventInit& aInit, ErrorResult& aRv) {
758   // Determining the source and origin should preceed attempting deserialization
759   // because on a "messageerror" event (i.e. when deserialization fails), the
760   // dispatched message needs to contain such an origin and source, per spec:
761   //
762   // "If this throws an exception, catch it, fire an event named messageerror
763   //  at destination, using MessageEvent, with the origin attribute initialized
764   //  to origin and the source attribute initialized to source, and then abort
765   //  these steps." - 6.4 of postMessage
766   //  See: https://w3c.github.io/ServiceWorker/#service-worker-postmessage
767   const RefPtr<ServiceWorker> serviceWorkerInstance =
768       aGlobal->GetOrCreateServiceWorker(aMessage.mServiceWorker);
769   if (serviceWorkerInstance) {
770     aInit.mSource.SetValue().SetAsServiceWorker() = serviceWorkerInstance;
771   }
772 
773   const nsresult rv =
774       FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin);
775   if (NS_FAILED(rv)) {
776     return Err(false);
777   }
778 
779   JS::Rooted<JS::Value> messageData(aCx);
780   aMessage.mClonedData.Read(aCx, &messageData, aRv);
781   if (aRv.Failed()) {
782     return Err(true);
783   }
784 
785   aInit.mData = messageData;
786 
787   if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) {
788     xpc::Throw(aCx, NS_ERROR_OUT_OF_MEMORY);
789     return Err(false);
790   }
791 
792   return Ok();
793 }
794 
795 }  // namespace dom
796 }  // namespace mozilla
797