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