ServiceWorker.cpp (10730B)
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/. */ 6 7 #include "ServiceWorker.h" 8 9 #include "ServiceWorkerChild.h" 10 #include "ServiceWorkerCloneData.h" 11 #include "ServiceWorkerManager.h" 12 #include "ServiceWorkerPrivate.h" 13 #include "ServiceWorkerRegistration.h" 14 #include "ServiceWorkerUtils.h" 15 #include "mozilla/BasePrincipal.h" 16 #include "mozilla/StaticPrefs_dom.h" 17 #include "mozilla/dom/ClientIPCTypes.h" 18 #include "mozilla/dom/ClientState.h" 19 #include "mozilla/dom/Document.h" 20 #include "mozilla/dom/MessagePortBinding.h" 21 #include "mozilla/dom/Navigator.h" 22 #include "mozilla/dom/Promise.h" 23 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" 24 #include "mozilla/dom/WorkerPrivate.h" 25 #include "mozilla/ipc/BackgroundChild.h" 26 #include "mozilla/ipc/PBackgroundChild.h" 27 #include "nsGlobalWindowInner.h" 28 #include "nsPIDOMWindow.h" 29 30 #ifdef XP_WIN 31 # undef PostMessage 32 #endif 33 34 using mozilla::ipc::BackgroundChild; 35 using mozilla::ipc::PBackgroundChild; 36 37 namespace mozilla::dom { 38 39 // static 40 already_AddRefed<ServiceWorker> ServiceWorker::Create( 41 nsIGlobalObject* aOwner, const ServiceWorkerDescriptor& aDescriptor) { 42 RefPtr<ServiceWorker> ref = new ServiceWorker(aOwner, aDescriptor); 43 return ref.forget(); 44 } 45 46 ServiceWorker::ServiceWorker(nsIGlobalObject* aGlobal, 47 const ServiceWorkerDescriptor& aDescriptor) 48 : DOMEventTargetHelper(aGlobal), 49 mDescriptor(aDescriptor), 50 mShutdown(false), 51 mLastNotifiedState(ServiceWorkerState::Installing) { 52 MOZ_DIAGNOSTIC_ASSERT(aGlobal); 53 54 PBackgroundChild* parentActor = 55 BackgroundChild::GetOrCreateForCurrentThread(); 56 if (NS_WARN_IF(!parentActor)) { 57 Shutdown(); 58 return; 59 } 60 61 RefPtr<ServiceWorkerChild> actor = ServiceWorkerChild::Create(); 62 if (NS_WARN_IF(!actor)) { 63 Shutdown(); 64 return; 65 } 66 67 PServiceWorkerChild* sentActor = 68 parentActor->SendPServiceWorkerConstructor(actor, aDescriptor.ToIPC()); 69 if (NS_WARN_IF(!sentActor)) { 70 Shutdown(); 71 return; 72 } 73 MOZ_DIAGNOSTIC_ASSERT(sentActor == actor); 74 75 mActor = std::move(actor); 76 mActor->SetOwner(this); 77 78 KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange); 79 80 // The error event handler is required by the spec currently, but is not used 81 // anywhere. Don't keep the object alive in that case. 82 83 // Attempt to get an existing binding object for the registration 84 // associated with this ServiceWorker. 85 RefPtr<ServiceWorkerRegistration> reg = 86 aGlobal->GetServiceWorkerRegistration(ServiceWorkerRegistrationDescriptor( 87 mDescriptor.RegistrationId(), mDescriptor.RegistrationVersion(), 88 mDescriptor.PrincipalInfo(), mDescriptor.Scope(), mDescriptor.Type(), 89 ServiceWorkerUpdateViaCache::Imports)); 90 91 if (reg) { 92 MaybeAttachToRegistration(reg); 93 // Following codes are commented since GetRegistration has no 94 // implementation. If we can not get an existing binding object, probably 95 // need to create one to associate to it. 96 // https://bugzilla.mozilla.org/show_bug.cgi?id=1769652 97 /* 98 } else { 99 100 RefPtr<ServiceWorker> self = this; 101 GetRegistration( 102 [self = std::move(self)]( 103 const ServiceWorkerRegistrationDescriptor& aDescriptor) { 104 nsIGlobalObject* global = self->GetParentObject(); 105 NS_ENSURE_TRUE_VOID(global); 106 RefPtr<ServiceWorkerRegistration> reg = 107 global->GetOrCreateServiceWorkerRegistration(aDescriptor); 108 self->MaybeAttachToRegistration(reg); 109 }, 110 [](ErrorResult&& aRv) { 111 // do nothing 112 aRv.SuppressException(); 113 }); 114 */ 115 } 116 } 117 118 ServiceWorker::~ServiceWorker() { Shutdown(); } 119 120 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorker, DOMEventTargetHelper, 121 mRegistration); 122 123 NS_IMPL_ADDREF_INHERITED(ServiceWorker, DOMEventTargetHelper) 124 NS_IMPL_RELEASE_INHERITED(ServiceWorker, DOMEventTargetHelper) 125 126 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorker) 127 NS_INTERFACE_MAP_ENTRY_CONCRETE(ServiceWorker) 128 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 129 130 JSObject* ServiceWorker::WrapObject(JSContext* aCx, 131 JS::Handle<JSObject*> aGivenProto) { 132 return ServiceWorker_Binding::Wrap(aCx, this, aGivenProto); 133 } 134 135 ServiceWorkerState ServiceWorker::State() const { return mDescriptor.State(); } 136 137 void ServiceWorker::SetState(ServiceWorkerState aState) { 138 NS_ENSURE_TRUE_VOID(aState >= mDescriptor.State()); 139 mDescriptor.SetState(aState); 140 } 141 142 void ServiceWorker::MaybeDispatchStateChangeEvent() { 143 if (mDescriptor.State() <= mLastNotifiedState || !GetParentObject()) { 144 return; 145 } 146 mLastNotifiedState = mDescriptor.State(); 147 148 DOMEventTargetHelper::DispatchTrustedEvent(u"statechange"_ns); 149 150 // Once we have transitioned to the redundant state then no 151 // more statechange events will occur. We can allow the DOM 152 // object to GC if script is not holding it alive. 153 if (mLastNotifiedState == ServiceWorkerState::Redundant) { 154 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange); 155 } 156 } 157 158 void ServiceWorker::GetScriptURL(nsString& aURL) const { 159 CopyUTF8toUTF16(mDescriptor.ScriptURL(), aURL); 160 } 161 162 void ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, 163 const Sequence<JSObject*>& aTransferable, 164 ErrorResult& aRv) { 165 // Step 6.1 of 166 // https://w3c.github.io/ServiceWorker/#service-worker-postmessage-options 167 // invokes 168 // https://w3c.github.io/ServiceWorker/#run-service-worker 169 // which returns failure in step 3 if the ServiceWorker state is redundant. 170 // This will result in the "in parallel" step 6.1 of postMessage itself early 171 // returning without starting the ServiceWorker and without throwing an error. 172 if (State() == ServiceWorkerState::Redundant) { 173 return; 174 } 175 176 nsIGlobalObject* global = GetOwnerGlobal(); 177 if (NS_WARN_IF(!global)) { 178 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 179 return; 180 } 181 182 if (!ServiceWorkersStorageAllowedForGlobal(global)) { 183 ServiceWorkerManager::LocalizeAndReportToAllClients( 184 mDescriptor.Scope(), "ServiceWorkerPostMessageStorageError", 185 nsTArray<nsString>{NS_ConvertUTF8toUTF16(mDescriptor.Scope())}); 186 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 187 return; 188 } 189 190 JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue()); 191 aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable, 192 &transferable); 193 if (aRv.Failed()) { 194 return; 195 } 196 197 // Window-to-SW messages do not allow memory sharing since they are not in the 198 // same agent cluster group, but we do not want to throw an error during the 199 // serialization. Because of this, ServiceWorkerCloneData will propagate an 200 // error message data if the SameProcess serialization is required. So that 201 // the receiver (service worker) knows that it needs to throw while 202 // deserialization and sharing memory objects are not propagated to the other 203 // process. 204 JS::CloneDataPolicy clonePolicy; 205 if (global->IsSharedMemoryAllowed()) { 206 clonePolicy.allowSharedMemoryObjects(); 207 } 208 209 RefPtr<ServiceWorkerCloneData> data = new ServiceWorkerCloneData(); 210 data->Write(aCx, aMessage, transferable, clonePolicy, aRv); 211 if (aRv.Failed()) { 212 return; 213 } 214 215 // If StructuredCloneData::Write() ended up deciding on a scope of SameProcess 216 // then we must convert this to an error on deserialization. This is because 217 // such payloads fundamentally can't be sent cross-process (they involve 218 // pointers / local resources). However, this will also correlate with the 219 // spec for situations like SharedArrayBuffer which are limited to being sent 220 // within the same agent cluster and where ServiceWorkers are always spawned 221 // in their own agent cluster. 222 if (data->CloneScope() == 223 StructuredCloneHolder::StructuredCloneScope::SameProcess) { 224 data->SetAsErrorMessageData(); 225 } 226 227 if (!mActor) { 228 return; 229 } 230 231 ClonedOrErrorMessageData clonedData; 232 if (!data->BuildClonedMessageData(clonedData)) { 233 return; 234 } 235 236 // ServiceWorkersStorageAllowedForGlobal will have already validated these as 237 // both being isSome(). 238 Maybe<ClientInfo> clientInfo = global->GetClientInfo(); 239 Maybe<ClientState> clientState = global->GetClientState(); 240 241 // If this global is a ServiceWorker, we need this global's 242 // ServiceWorkerDescriptor. While we normally try and normalize things 243 // through nsIGlobalObject, this is fairly one-off right now, so starting from 244 // worker-specific logic. 245 PostMessageSource source; 246 if (WorkerPrivate* wp = GetCurrentThreadWorkerPrivate()) { 247 if (wp->IsServiceWorker()) { 248 source = wp->GetServiceWorkerDescriptor().ToIPC(); 249 } else { 250 source = ClientInfoAndState(clientInfo.ref().ToIPC(), 251 clientState.ref().ToIPC()); 252 } 253 } else { 254 source = 255 ClientInfoAndState(clientInfo.ref().ToIPC(), clientState.ref().ToIPC()); 256 } 257 258 mActor->SendPostMessage(clonedData, source); 259 } 260 261 void ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, 262 const StructuredSerializeOptions& aOptions, 263 ErrorResult& aRv) { 264 PostMessage(aCx, aMessage, aOptions.mTransfer, aRv); 265 } 266 267 const ServiceWorkerDescriptor& ServiceWorker::Descriptor() const { 268 return mDescriptor; 269 } 270 271 void ServiceWorker::DisconnectFromOwner() { 272 DOMEventTargetHelper::DisconnectFromOwner(); 273 } 274 275 void ServiceWorker::RevokeActor(ServiceWorkerChild* aActor) { 276 MOZ_DIAGNOSTIC_ASSERT(mActor); 277 MOZ_DIAGNOSTIC_ASSERT(mActor == aActor); 278 mActor->RevokeOwner(this); 279 mActor = nullptr; 280 281 mShutdown = true; 282 } 283 284 void ServiceWorker::MaybeAttachToRegistration( 285 ServiceWorkerRegistration* aRegistration) { 286 MOZ_DIAGNOSTIC_ASSERT(aRegistration); 287 MOZ_DIAGNOSTIC_ASSERT(!mRegistration); 288 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(); 294 return; 295 } 296 297 mRegistration = aRegistration; 298 } 299 300 void ServiceWorker::Shutdown() { 301 if (mShutdown) { 302 return; 303 } 304 mShutdown = true; 305 306 if (mActor) { 307 mActor->RevokeOwner(this); 308 mActor->MaybeStartTeardown(); 309 mActor = nullptr; 310 } 311 } 312 313 } // namespace mozilla::dom