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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ServiceWorker.h"
9 #include "mozilla/dom/Document.h"
10 #include "nsGlobalWindowInner.h"
11 #include "nsPIDOMWindow.h"
12 #include "ServiceWorkerChild.h"
13 #include "ServiceWorkerCloneData.h"
14 #include "ServiceWorkerManager.h"
15 #include "ServiceWorkerPrivate.h"
16 #include "ServiceWorkerRegistration.h"
17 #include "ServiceWorkerUtils.h"
19 #include "mozilla/dom/ClientIPCTypes.h"
20 #include "mozilla/dom/ClientState.h"
21 #include "mozilla/dom/MessagePortBinding.h"
22 #include "mozilla/dom/Navigator.h"
23 #include "mozilla/dom/Promise.h"
24 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
25 #include "mozilla/dom/WorkerPrivate.h"
26 #include "mozilla/ipc/BackgroundChild.h"
27 #include "mozilla/ipc/PBackgroundChild.h"
28 #include "mozilla/BasePrincipal.h"
29 #include "mozilla/StaticPrefs_dom.h"
30 #include "mozilla/StorageAccess.h"
36 using mozilla::ipc::BackgroundChild
;
37 using mozilla::ipc::PBackgroundChild
;
39 namespace mozilla::dom
{
42 already_AddRefed
<ServiceWorker
> ServiceWorker::Create(
43 nsIGlobalObject
* aOwner
, const ServiceWorkerDescriptor
& aDescriptor
) {
44 RefPtr
<ServiceWorker
> ref
= new ServiceWorker(aOwner
, aDescriptor
);
48 ServiceWorker::ServiceWorker(nsIGlobalObject
* aGlobal
,
49 const ServiceWorkerDescriptor
& aDescriptor
)
50 : DOMEventTargetHelper(aGlobal
),
51 mDescriptor(aDescriptor
),
53 mLastNotifiedState(ServiceWorkerState::Installing
) {
54 MOZ_ASSERT(NS_IsMainThread());
55 MOZ_DIAGNOSTIC_ASSERT(aGlobal
);
57 PBackgroundChild
* parentActor
=
58 BackgroundChild::GetOrCreateForCurrentThread();
59 if (NS_WARN_IF(!parentActor
)) {
64 RefPtr
<ServiceWorkerChild
> actor
= ServiceWorkerChild::Create();
65 if (NS_WARN_IF(!actor
)) {
70 PServiceWorkerChild
* sentActor
=
71 parentActor
->SendPServiceWorkerConstructor(actor
, aDescriptor
.ToIPC());
72 if (NS_WARN_IF(!sentActor
)) {
76 MOZ_DIAGNOSTIC_ASSERT(sentActor
== actor
);
78 mActor
= std::move(actor
);
79 mActor
->SetOwner(this);
81 KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange
);
83 // The error event handler is required by the spec currently, but is not used
84 // anywhere. Don't keep the object alive in that case.
86 // Attempt to get an existing binding object for the registration
87 // associated with this ServiceWorker.
88 RefPtr
<ServiceWorkerRegistration
> reg
=
89 aGlobal
->GetServiceWorkerRegistration(ServiceWorkerRegistrationDescriptor(
90 mDescriptor
.RegistrationId(), mDescriptor
.RegistrationVersion(),
91 mDescriptor
.PrincipalInfo(), mDescriptor
.Scope(),
92 ServiceWorkerUpdateViaCache::Imports
));
95 MaybeAttachToRegistration(reg
);
96 // Following codes are commented since GetRegistration has no
97 // implementation. If we can not get an existing binding object, probably
98 // need to create one to associate to it.
99 // https://bugzilla.mozilla.org/show_bug.cgi?id=1769652
103 RefPtr<ServiceWorker> self = this;
105 [self = std::move(self)](
106 const ServiceWorkerRegistrationDescriptor& aDescriptor) {
107 nsIGlobalObject* global = self->GetParentObject();
108 NS_ENSURE_TRUE_VOID(global);
109 RefPtr<ServiceWorkerRegistration> reg =
110 global->GetOrCreateServiceWorkerRegistration(aDescriptor);
111 self->MaybeAttachToRegistration(reg);
113 [](ErrorResult&& aRv) {
115 aRv.SuppressException();
121 ServiceWorker::~ServiceWorker() {
122 MOZ_ASSERT(NS_IsMainThread());
126 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorker
, DOMEventTargetHelper
,
129 NS_IMPL_ADDREF_INHERITED(ServiceWorker
, DOMEventTargetHelper
)
130 NS_IMPL_RELEASE_INHERITED(ServiceWorker
, DOMEventTargetHelper
)
132 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorker
)
133 NS_INTERFACE_MAP_ENTRY_CONCRETE(ServiceWorker
)
134 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
136 JSObject
* ServiceWorker::WrapObject(JSContext
* aCx
,
137 JS::Handle
<JSObject
*> aGivenProto
) {
138 MOZ_ASSERT(NS_IsMainThread());
140 return ServiceWorker_Binding::Wrap(aCx
, this, aGivenProto
);
143 ServiceWorkerState
ServiceWorker::State() const { return mDescriptor
.State(); }
145 void ServiceWorker::SetState(ServiceWorkerState aState
) {
146 NS_ENSURE_TRUE_VOID(aState
>= mDescriptor
.State());
147 mDescriptor
.SetState(aState
);
150 void ServiceWorker::MaybeDispatchStateChangeEvent() {
151 if (mDescriptor
.State() <= mLastNotifiedState
|| !GetParentObject()) {
154 mLastNotifiedState
= mDescriptor
.State();
156 DOMEventTargetHelper::DispatchTrustedEvent(u
"statechange"_ns
);
158 // Once we have transitioned to the redundant state then no
159 // more statechange events will occur. We can allow the DOM
160 // object to GC if script is not holding it alive.
161 if (mLastNotifiedState
== ServiceWorkerState::Redundant
) {
162 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange
);
166 void ServiceWorker::GetScriptURL(nsString
& aURL
) const {
167 CopyUTF8toUTF16(mDescriptor
.ScriptURL(), aURL
);
170 void ServiceWorker::PostMessage(JSContext
* aCx
, JS::Handle
<JS::Value
> aMessage
,
171 const Sequence
<JSObject
*>& aTransferable
,
174 // https://w3c.github.io/ServiceWorker/#service-worker-postmessage-options
176 // https://w3c.github.io/ServiceWorker/#run-service-worker
177 // which returns failure in step 3 if the ServiceWorker state is redundant.
178 // This will result in the "in parallel" step 6.1 of postMessage itself early
179 // returning without starting the ServiceWorker and without throwing an error.
180 if (State() == ServiceWorkerState::Redundant
) {
184 nsPIDOMWindowInner
* window
= GetOwner();
185 if (NS_WARN_IF(!window
|| !window
->GetExtantDoc())) {
186 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
190 auto storageAllowed
= StorageAllowedForWindow(window
);
191 if (storageAllowed
!= StorageAccess::eAllow
&&
192 (!StaticPrefs::privacy_partition_serviceWorkers() ||
193 !StoragePartitioningEnabled(
194 storageAllowed
, window
->GetExtantDoc()->CookieJarSettings()))) {
195 ServiceWorkerManager::LocalizeAndReportToAllClients(
196 mDescriptor
.Scope(), "ServiceWorkerPostMessageStorageError",
197 nsTArray
<nsString
>{NS_ConvertUTF8toUTF16(mDescriptor
.Scope())});
198 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
202 Maybe
<ClientInfo
> clientInfo
= window
->GetClientInfo();
203 Maybe
<ClientState
> clientState
= window
->GetClientState();
204 if (NS_WARN_IF(clientInfo
.isNothing() || clientState
.isNothing())) {
205 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
209 JS::Rooted
<JS::Value
> transferable(aCx
, JS::UndefinedValue());
210 aRv
= nsContentUtils::CreateJSValueFromSequenceOfObject(aCx
, aTransferable
,
216 // Window-to-SW messages do not allow memory sharing since they are not in the
217 // same agent cluster group, but we do not want to throw an error during the
218 // serialization. Because of this, ServiceWorkerCloneData will propagate an
219 // error message data if the SameProcess serialization is required. So that
220 // the receiver (service worker) knows that it needs to throw while
221 // deserialization and sharing memory objects are not propagated to the other
223 JS::CloneDataPolicy clonePolicy
;
224 if (nsGlobalWindowInner::Cast(window
)->IsSharedMemoryAllowed()) {
225 clonePolicy
.allowSharedMemoryObjects();
228 RefPtr
<ServiceWorkerCloneData
> data
= new ServiceWorkerCloneData();
229 data
->Write(aCx
, aMessage
, transferable
, clonePolicy
, aRv
);
234 // The value of CloneScope() is set while StructuredCloneData::Write(). If the
235 // aValue contiains a shared memory object, then the scope will be restricted
236 // and thus return SameProcess. If not, it will return DifferentProcess.
238 // When we postMessage a shared memory object from a window to a service
239 // worker, the object must be sent from a cross-origin isolated process to
240 // another one. So, we mark mark this data as an error message data if the
241 // scope is limited to same process.
242 if (data
->CloneScope() ==
243 StructuredCloneHolder::StructuredCloneScope::SameProcess
) {
244 data
->SetAsErrorMessageData();
251 ClonedOrErrorMessageData clonedData
;
252 if (!data
->BuildClonedMessageData(clonedData
)) {
256 mActor
->SendPostMessage(
258 ClientInfoAndState(clientInfo
.ref().ToIPC(), clientState
.ref().ToIPC()));
261 void ServiceWorker::PostMessage(JSContext
* aCx
, JS::Handle
<JS::Value
> aMessage
,
262 const StructuredSerializeOptions
& aOptions
,
264 PostMessage(aCx
, aMessage
, aOptions
.mTransfer
, aRv
);
267 const ServiceWorkerDescriptor
& ServiceWorker::Descriptor() const {
271 void ServiceWorker::DisconnectFromOwner() {
272 DOMEventTargetHelper::DisconnectFromOwner();
275 void ServiceWorker::RevokeActor(ServiceWorkerChild
* aActor
) {
276 MOZ_DIAGNOSTIC_ASSERT(mActor
);
277 MOZ_DIAGNOSTIC_ASSERT(mActor
== aActor
);
278 mActor
->RevokeOwner(this);
284 void ServiceWorker::MaybeAttachToRegistration(
285 ServiceWorkerRegistration
* aRegistration
) {
286 MOZ_DIAGNOSTIC_ASSERT(aRegistration
);
287 MOZ_DIAGNOSTIC_ASSERT(!mRegistration
);
289 // If the registration no longer actually references this ServiceWorker
290 // then we must be in the redundant state.
291 if (!aRegistration
->Descriptor().HasWorker(mDescriptor
)) {
292 SetState(ServiceWorkerState::Redundant
);
293 MaybeDispatchStateChangeEvent();
297 mRegistration
= aRegistration
;
300 void ServiceWorker::Shutdown() {
307 mActor
->RevokeOwner(this);
308 mActor
->MaybeStartTeardown();
313 } // namespace mozilla::dom